mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 06:09:01 +00:00
Updates
This commit is contained in:
@@ -3,13 +3,12 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 56;
|
objectVersion = 60;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A272B03E71D00376976 /* MiniMode.swift */; };
|
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A272B03E71D00376976 /* MiniMode.swift */; };
|
||||||
C7045A322B068AD700376976 /* NewWin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A312B068AD700376976 /* NewWin.swift */; };
|
C7045A322B068AD700376976 /* NewWin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A312B068AD700376976 /* NewWin.swift */; };
|
||||||
C7045A342B068B2900376976 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A332B068B2900376976 /* UpdateView.swift */; };
|
|
||||||
C7045A362B068DD700376976 /* PermView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A352B068DD700376976 /* PermView.swift */; };
|
C7045A362B068DD700376976 /* PermView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A352B068DD700376976 /* PermView.swift */; };
|
||||||
C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71848432B8D2D600046CB13 /* ZombieView.swift */; };
|
C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71848432B8D2D600046CB13 /* ZombieView.swift */; };
|
||||||
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; };
|
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; };
|
||||||
@@ -17,12 +16,12 @@
|
|||||||
C736E7652C2DF1C9009EDB6A /* AppPathsFetch-Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */; };
|
C736E7652C2DF1C9009EDB6A /* AppPathsFetch-Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */; };
|
||||||
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */; };
|
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */; };
|
||||||
C74FDC632BCD833D00B8960F /* Conditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74FDC622BCD833D00B8960F /* Conditions.swift */; };
|
C74FDC632BCD833D00B8960F /* Conditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74FDC622BCD833D00B8960F /* Conditions.swift */; };
|
||||||
C75347642BE599FA00AAFCC3 /* PermissionChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75347632BE599FA00AAFCC3 /* PermissionChecker.swift */; };
|
|
||||||
C7575D392B0182DE006A600A /* PearcleanerSentinel in CopyFiles */ = {isa = PBXBuildFile; fileRef = C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
C7575D392B0182DE006A600A /* PearcleanerSentinel in CopyFiles */ = {isa = PBXBuildFile; fileRef = C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; };
|
C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; };
|
||||||
C76D084A2AF83C6E00D07867 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08492AF83C6E00D07867 /* General.swift */; };
|
C76D084A2AF83C6E00D07867 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08492AF83C6E00D07867 /* General.swift */; };
|
||||||
C76D08512AF850F300D07867 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08502AF850F300D07867 /* About.swift */; };
|
C76D08512AF850F300D07867 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08502AF850F300D07867 /* About.swift */; };
|
||||||
C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08542AF89CDE00D07867 /* RegularMode.swift */; };
|
C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08542AF89CDE00D07867 /* RegularMode.swift */; };
|
||||||
|
C76EDCD62C45DC82000CF29D /* AlinFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = C76EDCD52C45DC82000CF29D /* AlinFoundation */; };
|
||||||
C771CB762BD028AF00C33A18 /* SegmentedPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C771CB752BD028AF00C33A18 /* SegmentedPicker.swift */; };
|
C771CB762BD028AF00C33A18 /* SegmentedPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C771CB752BD028AF00C33A18 /* SegmentedPicker.swift */; };
|
||||||
C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */; };
|
C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */; };
|
||||||
C77B90082AF18E2F009CC655 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C77B90072AF18E2F009CC655 /* Assets.xcassets */; };
|
C77B90082AF18E2F009CC655 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C77B90072AF18E2F009CC655 /* Assets.xcassets */; };
|
||||||
@@ -35,7 +34,6 @@
|
|||||||
C78121662BC892A000BE06BD /* FinderOpen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C78121652BC892A000BE06BD /* FinderOpen.swift */; };
|
C78121662BC892A000BE06BD /* FinderOpen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C78121652BC892A000BE06BD /* FinderOpen.swift */; };
|
||||||
C781216B2BC892A000BE06BD /* FinderOpen.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C78121632BC892A000BE06BD /* FinderOpen.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
C781216B2BC892A000BE06BD /* FinderOpen.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C78121632BC892A000BE06BD /* FinderOpen.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
C79947962C34A794007279CC /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79947952C34A794007279CC /* Tips.swift */; };
|
C79947962C34A794007279CC /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79947952C34A794007279CC /* Tips.swift */; };
|
||||||
C79D05202BC99EB40083F976 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79D051F2BC99EB40083F976 /* ThemeManager.swift */; };
|
|
||||||
C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */; };
|
C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */; };
|
||||||
C7A2AE5D2B9644F300161540 /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7A2AE5C2B9644F300161540 /* FileWatcher */; };
|
C7A2AE5D2B9644F300161540 /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7A2AE5C2B9644F300161540 /* FileWatcher */; };
|
||||||
C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A9CE462B89164700EB6E78 /* Authorization.swift */; };
|
C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A9CE462B89164700EB6E78 /* Authorization.swift */; };
|
||||||
@@ -48,7 +46,6 @@
|
|||||||
C7DD49EA2BAB4D8100CCBA16 /* AppInfoFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49E92BAB4D8100CCBA16 /* AppInfoFetch.swift */; };
|
C7DD49EA2BAB4D8100CCBA16 /* AppInfoFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49E92BAB4D8100CCBA16 /* AppInfoFetch.swift */; };
|
||||||
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49ED2BAB7F6000CCBA16 /* ReversePathsFetch.swift */; };
|
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49ED2BAB7F6000CCBA16 /* ReversePathsFetch.swift */; };
|
||||||
C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49EF2BABA14400CCBA16 /* Folders.swift */; };
|
C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49EF2BABA14400CCBA16 /* Folders.swift */; };
|
||||||
C7DE17652BE54F4900197715 /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE17642BE54F4900197715 /* Updater.swift */; };
|
|
||||||
C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */; };
|
C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */; };
|
||||||
C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */; };
|
C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */; };
|
||||||
C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B02BA8A35500F13DE4 /* Interface.swift */; };
|
C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B02BA8A35500F13DE4 /* Interface.swift */; };
|
||||||
@@ -104,7 +101,6 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
C7045A272B03E71D00376976 /* MiniMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniMode.swift; sourceTree = "<group>"; };
|
C7045A272B03E71D00376976 /* MiniMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniMode.swift; sourceTree = "<group>"; };
|
||||||
C7045A312B068AD700376976 /* NewWin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewWin.swift; sourceTree = "<group>"; };
|
C7045A312B068AD700376976 /* NewWin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewWin.swift; sourceTree = "<group>"; };
|
||||||
C7045A332B068B2900376976 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
|
|
||||||
C7045A352B068DD700376976 /* PermView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermView.swift; sourceTree = "<group>"; };
|
C7045A352B068DD700376976 /* PermView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermView.swift; sourceTree = "<group>"; };
|
||||||
C71848432B8D2D600046CB13 /* ZombieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZombieView.swift; sourceTree = "<group>"; };
|
C71848432B8D2D600046CB13 /* ZombieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZombieView.swift; sourceTree = "<group>"; };
|
||||||
C72893042AFD42E600C8C1CD /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
|
C72893042AFD42E600C8C1CD /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
|
||||||
@@ -113,7 +109,6 @@
|
|||||||
C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPathsFetch-Async.swift"; sourceTree = "<group>"; };
|
C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPathsFetch-Async.swift"; sourceTree = "<group>"; };
|
||||||
C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathsFetch.swift; sourceTree = "<group>"; };
|
C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathsFetch.swift; sourceTree = "<group>"; };
|
||||||
C74FDC622BCD833D00B8960F /* Conditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conditions.swift; sourceTree = "<group>"; };
|
C74FDC622BCD833D00B8960F /* Conditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conditions.swift; sourceTree = "<group>"; };
|
||||||
C75347632BE599FA00AAFCC3 /* PermissionChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionChecker.swift; sourceTree = "<group>"; };
|
|
||||||
C76D08472AF83C3F00D07867 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = "<group>"; };
|
C76D08472AF83C3F00D07867 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = "<group>"; };
|
||||||
C76D08492AF83C6E00D07867 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = "<group>"; };
|
C76D08492AF83C6E00D07867 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = "<group>"; };
|
||||||
C76D08502AF850F300D07867 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
|
C76D08502AF850F300D07867 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
|
||||||
@@ -136,7 +131,6 @@
|
|||||||
C78121682BC892A000BE06BD /* FinderOpen.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FinderOpen.entitlements; sourceTree = "<group>"; };
|
C78121682BC892A000BE06BD /* FinderOpen.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FinderOpen.entitlements; sourceTree = "<group>"; };
|
||||||
C786F1A72C29E599001B3800 /* Builds */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Builds; sourceTree = SOURCE_ROOT; };
|
C786F1A72C29E599001B3800 /* Builds */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Builds; sourceTree = SOURCE_ROOT; };
|
||||||
C79947952C34A794007279CC /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = "<group>"; };
|
C79947952C34A794007279CC /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = "<group>"; };
|
||||||
C79D051F2BC99EB40083F976 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
|
|
||||||
C7A27E7F2AFD70DD00166168 /* PearcleanerSentinel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PearcleanerSentinel.entitlements; sourceTree = "<group>"; };
|
C7A27E7F2AFD70DD00166168 /* PearcleanerSentinel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PearcleanerSentinel.entitlements; sourceTree = "<group>"; };
|
||||||
C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.alienator88.PearcleanerSentinel.plist; sourceTree = "<group>"; };
|
C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.alienator88.PearcleanerSentinel.plist; sourceTree = "<group>"; };
|
||||||
C7A9CE462B89164700EB6E78 /* Authorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = "<group>"; };
|
C7A9CE462B89164700EB6E78 /* Authorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = "<group>"; };
|
||||||
@@ -149,7 +143,6 @@
|
|||||||
C7DD49E92BAB4D8100CCBA16 /* AppInfoFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoFetch.swift; sourceTree = "<group>"; };
|
C7DD49E92BAB4D8100CCBA16 /* AppInfoFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoFetch.swift; sourceTree = "<group>"; };
|
||||||
C7DD49ED2BAB7F6000CCBA16 /* ReversePathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReversePathsFetch.swift; sourceTree = "<group>"; };
|
C7DD49ED2BAB7F6000CCBA16 /* ReversePathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReversePathsFetch.swift; sourceTree = "<group>"; };
|
||||||
C7DD49EF2BABA14400CCBA16 /* Folders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Folders.swift; sourceTree = "<group>"; };
|
C7DD49EF2BABA14400CCBA16 /* Folders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Folders.swift; sourceTree = "<group>"; };
|
||||||
C7DE17642BE54F4900197715 /* Updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; };
|
|
||||||
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarItem.swift; sourceTree = "<group>"; };
|
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarItem.swift; sourceTree = "<group>"; };
|
||||||
C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionBuilderView.swift; sourceTree = "<group>"; };
|
C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionBuilderView.swift; sourceTree = "<group>"; };
|
||||||
C7ED86B02BA8A35500F13DE4 /* Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interface.swift; sourceTree = "<group>"; };
|
C7ED86B02BA8A35500F13DE4 /* Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interface.swift; sourceTree = "<group>"; };
|
||||||
@@ -172,6 +165,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
C76EDCD62C45DC82000CF29D /* AlinFoundation in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -189,7 +183,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C7045A312B068AD700376976 /* NewWin.swift */,
|
C7045A312B068AD700376976 /* NewWin.swift */,
|
||||||
C7045A332B068B2900376976 /* UpdateView.swift */,
|
|
||||||
C7D15BBD2BE85F4D007D57CF /* FeatureView.swift */,
|
C7D15BBD2BE85F4D007D57CF /* FeatureView.swift */,
|
||||||
C7045A352B068DD700376976 /* PermView.swift */,
|
C7045A352B068DD700376976 /* PermView.swift */,
|
||||||
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */,
|
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */,
|
||||||
@@ -216,9 +209,6 @@
|
|||||||
C7A9CE462B89164700EB6E78 /* Authorization.swift */,
|
C7A9CE462B89164700EB6E78 /* Authorization.swift */,
|
||||||
C7ACADE52B92A737000B5845 /* Features.swift */,
|
C7ACADE52B92A737000B5845 /* Features.swift */,
|
||||||
C7ED86B22BA8ED8800F13DE4 /* Trash.swift */,
|
C7ED86B22BA8ED8800F13DE4 /* Trash.swift */,
|
||||||
C79D051F2BC99EB40083F976 /* ThemeManager.swift */,
|
|
||||||
C7DE17642BE54F4900197715 /* Updater.swift */,
|
|
||||||
C75347632BE599FA00AAFCC3 /* PermissionChecker.swift */,
|
|
||||||
);
|
);
|
||||||
path = Logic;
|
path = Logic;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -374,6 +364,7 @@
|
|||||||
);
|
);
|
||||||
name = Pearcleaner;
|
name = Pearcleaner;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
C76EDCD52C45DC82000CF29D /* AlinFoundation */,
|
||||||
);
|
);
|
||||||
productName = PeakPurge;
|
productName = PeakPurge;
|
||||||
productReference = C77B90002AF18E2E009CC655 /* Pearcleaner.app */;
|
productReference = C77B90002AF18E2E009CC655 /* Pearcleaner.app */;
|
||||||
@@ -428,6 +419,7 @@
|
|||||||
mainGroup = C77B8FF72AF18E2E009CC655;
|
mainGroup = C77B8FF72AF18E2E009CC655;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
C7A2AE5B2B9644DA00161540 /* XCRemoteSwiftPackageReference "FileWatcher" */,
|
C7A2AE5B2B9644DA00161540 /* XCRemoteSwiftPackageReference "FileWatcher" */,
|
||||||
|
C76EDCD42C45DC82000CF29D /* XCLocalSwiftPackageReference "../AlinFoundation" */,
|
||||||
);
|
);
|
||||||
productRefGroup = C77B90012AF18E2E009CC655 /* Products */;
|
productRefGroup = C77B90012AF18E2E009CC655 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -477,10 +469,7 @@
|
|||||||
C79947962C34A794007279CC /* Tips.swift in Sources */,
|
C79947962C34A794007279CC /* Tips.swift in Sources */,
|
||||||
C76D08482AF83C3F00D07867 /* Update.swift in Sources */,
|
C76D08482AF83C3F00D07867 /* Update.swift in Sources */,
|
||||||
C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */,
|
C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */,
|
||||||
C7045A342B068B2900376976 /* UpdateView.swift in Sources */,
|
|
||||||
C77B90162AF19377009CC655 /* AppState.swift in Sources */,
|
C77B90162AF19377009CC655 /* AppState.swift in Sources */,
|
||||||
C75347642BE599FA00AAFCC3 /* PermissionChecker.swift in Sources */,
|
|
||||||
C7DE17652BE54F4900197715 /* Updater.swift in Sources */,
|
|
||||||
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */,
|
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */,
|
||||||
C7045A322B068AD700376976 /* NewWin.swift in Sources */,
|
C7045A322B068AD700376976 /* NewWin.swift in Sources */,
|
||||||
C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */,
|
C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */,
|
||||||
@@ -504,7 +493,6 @@
|
|||||||
C77B901E2AF1A9DB009CC655 /* SettingsWindow.swift in Sources */,
|
C77B901E2AF1A9DB009CC655 /* SettingsWindow.swift in Sources */,
|
||||||
C771CB762BD028AF00C33A18 /* SegmentedPicker.swift in Sources */,
|
C771CB762BD028AF00C33A18 /* SegmentedPicker.swift in Sources */,
|
||||||
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */,
|
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */,
|
||||||
C79D05202BC99EB40083F976 /* ThemeManager.swift in Sources */,
|
|
||||||
C74FDC632BCD833D00B8960F /* Conditions.swift in Sources */,
|
C74FDC632BCD833D00B8960F /* Conditions.swift in Sources */,
|
||||||
C7ACADE62B92A737000B5845 /* Features.swift in Sources */,
|
C7ACADE62B92A737000B5845 /* Features.swift in Sources */,
|
||||||
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */,
|
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */,
|
||||||
@@ -905,6 +893,13 @@
|
|||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
|
C76EDCD42C45DC82000CF29D /* XCLocalSwiftPackageReference "../AlinFoundation" */ = {
|
||||||
|
isa = XCLocalSwiftPackageReference;
|
||||||
|
relativePath = ../AlinFoundation;
|
||||||
|
};
|
||||||
|
/* End XCLocalSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
C7A2AE5B2B9644DA00161540 /* XCRemoteSwiftPackageReference "FileWatcher" */ = {
|
C7A2AE5B2B9644DA00161540 /* XCRemoteSwiftPackageReference "FileWatcher" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
@@ -917,6 +912,10 @@
|
|||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
C76EDCD52C45DC82000CF29D /* AlinFoundation */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = AlinFoundation;
|
||||||
|
};
|
||||||
C7A2AE5C2B9644F300161540 /* FileWatcher */ = {
|
C7A2AE5C2B9644F300161540 /* FileWatcher */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = C7A2AE5B2B9644DA00161540 /* XCRemoteSwiftPackageReference "FileWatcher" */;
|
package = C7A2AE5B2B9644DA00161540 /* XCRemoteSwiftPackageReference "FileWatcher" */;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"originHash" : "2070782a951fcb2eef783fefb42bac8d9dafc68491164378649d1525b43cbc83",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "filewatcher",
|
"identity" : "filewatcher",
|
||||||
@@ -10,5 +11,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,22 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct AppCommands: Commands {
|
struct AppCommands: Commands {
|
||||||
|
|
||||||
let appState: AppState
|
let appState: AppState
|
||||||
let locations: Locations
|
let locations: Locations
|
||||||
let fsm: FolderSettingsManager
|
let fsm: FolderSettingsManager
|
||||||
|
let updater: Updater
|
||||||
|
let themeManager: ThemeManager
|
||||||
|
|
||||||
init(appState: AppState, locations: Locations, fsm: FolderSettingsManager) {
|
init(appState: AppState, locations: Locations, fsm: FolderSettingsManager, updater: Updater, themeManager: ThemeManager) {
|
||||||
self.appState = appState
|
self.appState = appState
|
||||||
self.locations = locations
|
self.locations = locations
|
||||||
self.fsm = fsm
|
self.fsm = fsm
|
||||||
|
self.updater = updater
|
||||||
|
self.themeManager = themeManager
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some Commands {
|
var body: some Commands {
|
||||||
@@ -25,7 +30,7 @@ struct AppCommands: Commands {
|
|||||||
CommandGroup(replacing: .appInfo) {
|
CommandGroup(replacing: .appInfo) {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
loadGithubReleases(appState: appState, manual: true)
|
updater.checkForUpdates()
|
||||||
} label: {
|
} label: {
|
||||||
Text("Check for Updates")
|
Text("Check for Updates")
|
||||||
}
|
}
|
||||||
@@ -55,12 +60,15 @@ struct AppCommands: Commands {
|
|||||||
|
|
||||||
Button
|
Button
|
||||||
{
|
{
|
||||||
|
if appState.currentView != .zombie {
|
||||||
undoTrash(appState: appState) {
|
undoTrash(appState: appState) {
|
||||||
reloadAppsList(appState: appState, fsm: fsm)
|
reloadAppsList(appState: appState, fsm: fsm)
|
||||||
for app in appState.trashedFiles {
|
for app in appState.trashedFiles {
|
||||||
AppPathFinder(appInfo: app, appState: appState, locations: locations, undo: true).findPaths()
|
AppPathFinder(appInfo: app, appState: appState, locations: locations, undo: true).findPaths()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} label: {
|
} label: {
|
||||||
Label("Undo Removal", systemImage: "clear")
|
Label("Undo Removal", systemImage: "clear")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
class AppInfoFetcher {
|
class AppInfoFetcher {
|
||||||
static let fileManager = FileManager.default
|
static let fileManager = FileManager.default
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import AppKit
|
import AppKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
class AppPathFinder {
|
class AppPathFinder {
|
||||||
private var appInfo: AppInfo
|
private var appInfo: AppInfo
|
||||||
|
|||||||
@@ -22,15 +22,14 @@ class AppState: ObservableObject {
|
|||||||
@Published var currentView = CurrentDetailsView.empty
|
@Published var currentView = CurrentDetailsView.empty
|
||||||
@Published var showAlert: Bool = false
|
@Published var showAlert: Bool = false
|
||||||
@Published var sidebar: Bool = true
|
@Published var sidebar: Bool = true
|
||||||
@Published var releases = [Release]()
|
|
||||||
@Published var progressBar: (String, Double) = ("Ready", 0.0)
|
@Published var progressBar: (String, Double) = ("Ready", 0.0)
|
||||||
@Published var reload: Bool = false
|
@Published var reload: Bool = false
|
||||||
@Published var showProgress: Bool = false
|
@Published var showProgress: Bool = false
|
||||||
@Published var finderExtensionEnabled: Bool = false
|
@Published var finderExtensionEnabled: Bool = false
|
||||||
@Published var updateAvailable: Bool = false
|
// @Published var updateAvailable: Bool = false
|
||||||
@Published var featureAvailable: Bool = false
|
@Published var featureAvailable: Bool = false
|
||||||
@Published var permissionsOkay: Bool = true
|
// @Published var permissionsOkay: Bool = true
|
||||||
@Published var permissionResults: PermissionsCheckResults?
|
// @Published var permissionResults: PermissionsCheckResults?
|
||||||
@Published var showUninstallAlert: Bool = false
|
@Published var showUninstallAlert: Bool = false
|
||||||
@Published var oneShotMode: Bool = false
|
@Published var oneShotMode: Bool = false
|
||||||
@Published var showConditionBuilder: Bool = false
|
@Published var showConditionBuilder: Bool = false
|
||||||
@@ -182,8 +181,6 @@ enum CurrentDetailsView:Int
|
|||||||
|
|
||||||
enum NewWindow:Int
|
enum NewWindow:Int
|
||||||
{
|
{
|
||||||
case update
|
|
||||||
case no_update
|
|
||||||
case perm
|
case perm
|
||||||
case feature
|
case feature
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
class DeeplinkManager {
|
class DeeplinkManager {
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
|
|
||||||
func getFeatures(appState: AppState, features: Binding<String>) {
|
func getFeatures(appState: AppState, features: Binding<String>) {
|
||||||
let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/contents/features.json")!
|
let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/contents/features.json")!
|
||||||
@@ -78,7 +80,7 @@ struct FeatureNotificationView: View {
|
|||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
|
|
||||||
|
|
||||||
Text("Show")
|
Text("Check")
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -103,7 +105,7 @@ struct FeatureNotificationView: View {
|
|||||||
}
|
}
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.background(Color("mode").opacity(0.05))
|
.background(.primary.opacity(0.05))
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class Locations: ObservableObject {
|
|||||||
self.tempDir = tempDir
|
self.tempDir = tempDir
|
||||||
|
|
||||||
self.apps = Category(name: "Apps", paths: [
|
self.apps = Category(name: "Apps", paths: [
|
||||||
"\(home)",
|
// "\(home)", //MARK: Disable searching for files in user's home folder
|
||||||
"\(home)/Library",
|
"\(home)/Library",
|
||||||
"\(home)/Library/Application Scripts",
|
"\(home)/Library/Application Scripts",
|
||||||
"\(home)/Library/Application Support",
|
"\(home)/Library/Application Support",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
|
|
||||||
// Get all apps from /Applications and ~/Applications
|
// Get all apps from /Applications and ~/Applications
|
||||||
@@ -212,14 +213,14 @@ func moveFilesToTrash(appState: AppState, at fileURLs: [URL], completion: @escap
|
|||||||
if let scriptObject = NSAppleScript(source: scriptSource) {
|
if let scriptObject = NSAppleScript(source: scriptSource) {
|
||||||
let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error)
|
let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error)
|
||||||
if let error = error {
|
if let error = error {
|
||||||
checkAllPermissions(appState: appState) { results in
|
// checkAllPermissions(appState: appState) { results in
|
||||||
appState.permissionResults = results
|
// appState.permissionResults = results
|
||||||
if !results.allPermissionsGranted {
|
// if !results.allPermissionsGranted {
|
||||||
updateOnMain {
|
// updateOnMain {
|
||||||
appState.permissionsOkay = false
|
// appState.permissionsOkay = false
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
printOS("Trash Error: \(error)")
|
printOS("Trash Error: \(error)")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completion(false) // Indicate failure
|
completion(false) // Indicate failure
|
||||||
@@ -261,14 +262,14 @@ func undoTrash(appState: AppState, completion: @escaping () -> Void = {}) {
|
|||||||
if let scriptObject = NSAppleScript(source: scriptSource) {
|
if let scriptObject = NSAppleScript(source: scriptSource) {
|
||||||
let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error)
|
let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error)
|
||||||
if let error = error {
|
if let error = error {
|
||||||
checkAllPermissions(appState: appState) { results in
|
// checkAllPermissions(appState: appState) { results in
|
||||||
appState.permissionResults = results
|
// appState.permissionResults = results
|
||||||
if !results.allPermissionsGranted {
|
// if !results.allPermissionsGranted {
|
||||||
updateOnMain {
|
// updateOnMain {
|
||||||
appState.permissionsOkay = false
|
// appState.permissionsOkay = false
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
printOS("Undo Trash Error: \(error)")
|
printOS("Undo Trash Error: \(error)")
|
||||||
} else if let outputString = output.stringValue {
|
} else if let outputString = output.stringValue {
|
||||||
printOS(outputString)
|
printOS(outputString)
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
//
|
|
||||||
// PermissionChecker.swift
|
|
||||||
// Pearcleaner
|
|
||||||
//
|
|
||||||
// Created by Alin Lupascu on 5/3/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
//import EventKit
|
|
||||||
|
|
||||||
struct PermissionsCheckResults {
|
|
||||||
var fullDiskAccess: Bool
|
|
||||||
var accessibility: Bool
|
|
||||||
var automation: Bool
|
|
||||||
|
|
||||||
// Computed property to check if all permissions are granted
|
|
||||||
var allPermissionsGranted: Bool {
|
|
||||||
return fullDiskAccess && accessibility && automation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkAllPermissions(appState: AppState, completion: @escaping (PermissionsCheckResults) -> Void) {
|
|
||||||
let dispatchGroup = DispatchGroup()
|
|
||||||
|
|
||||||
// Check Full Disk Access
|
|
||||||
var fullDiskAccess = false
|
|
||||||
@AppStorage("settings.permissions.hasLaunched") var hasLaunched: Bool = false
|
|
||||||
|
|
||||||
let process = Process()
|
|
||||||
process.launchPath = "/usr/bin/sqlite3"
|
|
||||||
process.arguments = ["/Library/Application Support/com.apple.TCC/TCC.db", "select client from access where auth_value and service = 'kTCCServiceSystemPolicyAllFiles' and client = 'com.alienator88.Pearcleaner'"]
|
|
||||||
let pipe = Pipe()
|
|
||||||
process.standardOutput = pipe
|
|
||||||
process.launch()
|
|
||||||
process.waitUntilExit() // Ensure process completes
|
|
||||||
|
|
||||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
||||||
let output = String(data: data, encoding: .utf8)
|
|
||||||
fullDiskAccess = (output?.contains("com.alienator88.Pearcleaner") ?? false)
|
|
||||||
|
|
||||||
// Check Accessibility
|
|
||||||
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true]
|
|
||||||
let accessibilityEnabled = AXIsProcessTrustedWithOptions(options)
|
|
||||||
|
|
||||||
// Check Automation Permission
|
|
||||||
var automationAccess = false
|
|
||||||
dispatchGroup.enter()
|
|
||||||
checkAutomationPermission(appState: appState) { success in
|
|
||||||
automationAccess = success
|
|
||||||
dispatchGroup.leave()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Wait for all async checks to complete
|
|
||||||
dispatchGroup.notify(queue: .main) {
|
|
||||||
let results = PermissionsCheckResults(
|
|
||||||
fullDiskAccess: fullDiskAccess,
|
|
||||||
accessibility: accessibilityEnabled,
|
|
||||||
automation: automationAccess
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if any permission is denied and show window
|
|
||||||
if !(results.fullDiskAccess && results.accessibility && results.automation) {
|
|
||||||
updateOnMain {
|
|
||||||
appState.permissionsOkay = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
completion(results)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Check Finder Automation permission
|
|
||||||
func checkAutomationPermission(appState: AppState, completion: @escaping (Bool) -> Void) {
|
|
||||||
DispatchQueue.global(qos: .background).async {
|
|
||||||
let scriptText = "tell application \"Finder\" to return name of home"
|
|
||||||
var error: NSDictionary?
|
|
||||||
if let script = NSAppleScript(source: scriptText) {
|
|
||||||
let _ = script.executeAndReturnError(&error)
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(error == nil)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct PermissionsNotificationView: View {
|
|
||||||
let appState: AppState
|
|
||||||
@State private var hovered: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Text("Missing Permissions!")
|
|
||||||
.font(.callout)
|
|
||||||
.opacity(0.5)
|
|
||||||
.padding(.leading, 7)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
settingsLinkButton
|
|
||||||
}
|
|
||||||
.frame(height: 30)
|
|
||||||
.padding(5)
|
|
||||||
.background(Color("mode").opacity(0.05))
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.bottom)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var settingsLinkButton: some View {
|
|
||||||
if #available(macOS 14.0, *) {
|
|
||||||
SettingsLink {
|
|
||||||
labelContent
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
.padding(4)
|
|
||||||
.background(Color.red)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
|
||||||
.onHover { hover in
|
|
||||||
withAnimation {
|
|
||||||
hovered = hover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.help("Check all permissions")
|
|
||||||
} else {
|
|
||||||
Button(action: {
|
|
||||||
if #available(macOS 13.0, *) {
|
|
||||||
NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
labelContent
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
.padding(4)
|
|
||||||
.background(Color.red)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
|
||||||
.onHover { hover in
|
|
||||||
withAnimation {
|
|
||||||
hovered = hover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.help("Check all permissions")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var labelContent: some View {
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Image(systemName: !hovered ? "lock" : "lock.fill")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 14, height: 14)
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
Text("Check")
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
}
|
|
||||||
.padding(3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
class ReversePathsSearcher {
|
class ReversePathsSearcher {
|
||||||
private let appState: AppState
|
private let appState: AppState
|
||||||
|
|||||||
+17
-651
@@ -7,88 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
struct SimpleButtonStyle: ButtonStyle {
|
|
||||||
@State private var hovered = false
|
|
||||||
let icon: String
|
|
||||||
let iconFlip: String
|
|
||||||
let label: String
|
|
||||||
let help: String
|
|
||||||
let color: Color
|
|
||||||
let size: CGFloat
|
|
||||||
let padding: CGFloat
|
|
||||||
let rotate: Bool
|
|
||||||
|
|
||||||
init(icon: String, iconFlip: String = "", label: String = "", help: String, color: Color = Color("mode"), size: CGFloat = 20, padding: CGFloat = 5, rotate: Bool = false) {
|
|
||||||
self.icon = icon
|
|
||||||
self.iconFlip = iconFlip
|
|
||||||
self.label = label
|
|
||||||
self.help = help
|
|
||||||
self.color = color
|
|
||||||
self.size = size
|
|
||||||
self.padding = padding
|
|
||||||
self.rotate = rotate
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeBody(configuration: Self.Configuration) -> some View {
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Image(systemName: (hovered && !iconFlip.isEmpty) ? iconFlip : icon)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: size, height: size)
|
|
||||||
.scaleEffect(hovered ? 1.1 : 1.0)
|
|
||||||
.rotationEffect(.degrees(rotate ? (hovered ? 90 : 0) : 0))
|
|
||||||
.animation(.easeInOut(duration: 0.2), value: hovered)
|
|
||||||
if !label.isEmpty {
|
|
||||||
Text(label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.foregroundColor(hovered ? color : color.opacity(0.5))
|
|
||||||
.padding(padding)
|
|
||||||
.onHover { hovering in
|
|
||||||
withAnimation() {
|
|
||||||
hovered = hovering
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
|
||||||
.help(help)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct RegularButtonStyle: ButtonStyle {
|
|
||||||
let icon: String
|
|
||||||
let label: String
|
|
||||||
let help: String
|
|
||||||
@State private var hovered = false
|
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Image(systemName: icon).foregroundColor(Color("mode")).padding(.leading, 2).padding(.trailing, 0)
|
|
||||||
Text(label).textCase(.uppercase).foregroundColor(Color("mode")).fontWeight(.bold).padding(.trailing, 2)
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(8)
|
|
||||||
.background(!configuration.isPressed ? hovered ? Color("mode").opacity(0.4) : Color("mode").opacity(0) : Color("mode").opacity(0.5))
|
|
||||||
.cornerRadius(6)
|
|
||||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
|
||||||
.animation(.easeOut(duration: 0.2), value: configuration.isPressed)
|
|
||||||
.onHover { hovering in
|
|
||||||
withAnimation(Animation.easeIn(duration: 0.15)) {
|
|
||||||
hovered = hovering
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: 6)
|
|
||||||
.strokeBorder(Color("mode").opacity(0.5), lineWidth: 1)
|
|
||||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
|
||||||
)
|
|
||||||
.help(help)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct ResetSettingsButtonStyle: ButtonStyle {
|
struct ResetSettingsButtonStyle: ButtonStyle {
|
||||||
@Binding var isResetting: Bool
|
@Binding var isResetting: Bool
|
||||||
@@ -105,13 +24,13 @@ struct ResetSettingsButtonStyle: ButtonStyle {
|
|||||||
.frame(width: 15)
|
.frame(width: 15)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "gear")
|
Image(systemName: "gear")
|
||||||
.foregroundColor(Color("mode")).padding(.leading, 2).padding(.trailing, 0)
|
.foregroundColor(.primary).padding(.leading, 2).padding(.trailing, 0)
|
||||||
}
|
}
|
||||||
Text(label)
|
Text(label)
|
||||||
.textCase(.uppercase).foregroundColor(Color("mode")).fontWeight(.bold).padding(.trailing, 2)
|
.textCase(.uppercase).foregroundColor(.primary).fontWeight(.bold).padding(.trailing, 2)
|
||||||
}
|
}
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.background(!configuration.isPressed ? hovered ? Color("mode").opacity(0.4) : Color("mode").opacity(0) : Color("mode").opacity(0.5))
|
.background(!configuration.isPressed ? hovered ? Color.primary.opacity(0.4) : Color.primary.opacity(0) : Color.primary.opacity(0.5))
|
||||||
.cornerRadius(6)
|
.cornerRadius(6)
|
||||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
||||||
.animation(.easeOut(duration: 0.2), value: configuration.isPressed)
|
.animation(.easeOut(duration: 0.2), value: configuration.isPressed)
|
||||||
@@ -122,7 +41,7 @@ struct ResetSettingsButtonStyle: ButtonStyle {
|
|||||||
}
|
}
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 6)
|
RoundedRectangle(cornerRadius: 6)
|
||||||
.strokeBorder(Color("mode").opacity(0.5), lineWidth: 1)
|
.strokeBorder(.primary.opacity(0.5), lineWidth: 1)
|
||||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
||||||
)
|
)
|
||||||
.help(help)
|
.help(help)
|
||||||
@@ -131,18 +50,18 @@ struct ResetSettingsButtonStyle: ButtonStyle {
|
|||||||
|
|
||||||
|
|
||||||
struct SimpleCheckboxToggleStyle: ToggleStyle {
|
struct SimpleCheckboxToggleStyle: ToggleStyle {
|
||||||
@EnvironmentObject private var themeSettings: ThemeSettings
|
@EnvironmentObject private var themeManager: ThemeManager
|
||||||
@State private var isHovered: Bool = false
|
@State private var isHovered: Bool = false
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
RoundedRectangle(cornerRadius: 4)
|
RoundedRectangle(cornerRadius: 4)
|
||||||
.fill(themeSettings.themeColor.darker(by: 5))
|
.fill(themeManager.pickerColor.adjustBrightness(5))
|
||||||
.frame(width: 18, height: 18)
|
.frame(width: 18, height: 18)
|
||||||
.overlay {
|
.overlay {
|
||||||
if configuration.isOn {
|
if configuration.isOn {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.foregroundStyle(Color("mode"))
|
.foregroundStyle(.primary)
|
||||||
.scaleEffect(isHovered ? 0.8 : 1.0)
|
.scaleEffect(isHovered ? 0.8 : 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,150 +79,6 @@ struct SimpleCheckboxToggleStyle: ToggleStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct InfoButton: View {
|
|
||||||
@State private var isPopoverPresented: Bool = false
|
|
||||||
let text: String
|
|
||||||
let color: Color
|
|
||||||
let label: String
|
|
||||||
let warning: Bool
|
|
||||||
let edge: Edge
|
|
||||||
|
|
||||||
init(text: String, color: Color = Color("mode"), label: String = "", warning: Bool = false, edge: Edge = .bottom) {
|
|
||||||
self.text = text
|
|
||||||
self.color = color
|
|
||||||
self.label = label
|
|
||||||
self.warning = warning
|
|
||||||
self.edge = edge
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button(action: {
|
|
||||||
self.isPopoverPresented.toggle()
|
|
||||||
}) {
|
|
||||||
HStack(alignment: .center, spacing: 5) {
|
|
||||||
Image(systemName: !warning ? "info.circle.fill" : "exclamationmark.triangle.fill")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 14, height: 14)
|
|
||||||
.foregroundColor(!warning ? color.opacity(0.7) : color)
|
|
||||||
.frame(height: 16)
|
|
||||||
if !label.isEmpty {
|
|
||||||
Text(label)
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundColor(color.opacity(0.7))
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
.onHover { isHovered in
|
|
||||||
if isHovered {
|
|
||||||
NSCursor.pointingHand.push()
|
|
||||||
} else {
|
|
||||||
NSCursor.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.popover(isPresented: $isPopoverPresented, arrowEdge: edge) {
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
Text(text)
|
|
||||||
.font(.callout)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding()
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.frame(width: 300)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct InfoButtonPerms: View {
|
|
||||||
@State private var isPopoverPresented: Bool = false
|
|
||||||
let color: Color
|
|
||||||
let label: String
|
|
||||||
let warning: Bool
|
|
||||||
|
|
||||||
init(color: Color = Color("mode"), label: String = "", warning: Bool = false) {
|
|
||||||
self.color = color
|
|
||||||
self.label = label
|
|
||||||
self.warning = warning
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button(action: {
|
|
||||||
self.isPopoverPresented.toggle()
|
|
||||||
}) {
|
|
||||||
HStack(alignment: .center, spacing: 5) {
|
|
||||||
Image(systemName: !warning ? "info.circle.fill" : "exclamationmark.triangle.fill")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 14, height: 14)
|
|
||||||
.foregroundColor(!warning ? color.opacity(0.7) : color)
|
|
||||||
.frame(height: 16)
|
|
||||||
if !label.isEmpty {
|
|
||||||
Text(label)
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundColor(color.opacity(0.7))
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
.onHover { isHovered in
|
|
||||||
if isHovered {
|
|
||||||
NSCursor.pointingHand.push()
|
|
||||||
} else {
|
|
||||||
NSCursor.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.popover(isPresented: $isPopoverPresented, arrowEdge: .bottom) {
|
|
||||||
VStack(alignment: .leading, spacing: 15) {
|
|
||||||
|
|
||||||
HStack(alignment: .top, spacing: 20) {
|
|
||||||
Image(systemName: "externaldrive")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
Text("Full Disk permission to access files/folders in system paths")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
}
|
|
||||||
|
|
||||||
HStack(alignment: .top, spacing: 20) {
|
|
||||||
Image(systemName: "accessibility")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
Text("Accessibility permission to allow execution of AppleScript")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
}
|
|
||||||
|
|
||||||
HStack(alignment: .top, spacing: 20) {
|
|
||||||
Image(systemName: "gearshape.2")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
Text("Automation permission to perform delete actions via Finder")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct UninstallButton: ButtonStyle {
|
struct UninstallButton: ButtonStyle {
|
||||||
@State private var hovered: Bool = false
|
@State private var hovered: Bool = false
|
||||||
@@ -371,40 +146,6 @@ struct RescanButton: ButtonStyle {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct WarningPopoverView: View {
|
|
||||||
var label: String
|
|
||||||
var bodyText: String
|
|
||||||
|
|
||||||
@Binding var isPresented: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "exclamationmark.triangle.fill")
|
|
||||||
.foregroundStyle(.red)
|
|
||||||
.popover(isPresented: $isPresented, arrowEdge: .top) {
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
Text(bodyText)
|
|
||||||
.font(.callout)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding()
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.frame(width: 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(label)
|
|
||||||
.foregroundStyle(Color.red)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
isPresented.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public struct NewFeatureView: View {
|
public struct NewFeatureView: View {
|
||||||
var text: String
|
var text: String
|
||||||
var mini: Bool
|
var mini: Bool
|
||||||
@@ -425,7 +166,7 @@ public struct NewFeatureView: View {
|
|||||||
showFeature = false
|
showFeature = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close", color: (Color("mode").opacity(0.5))))
|
.buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close", color: (.primary.opacity(0.5))))
|
||||||
.onHover { isHovered in
|
.onHover { isHovered in
|
||||||
if isHovered {
|
if isHovered {
|
||||||
NSCursor.pointingHand.push()
|
NSCursor.pointingHand.push()
|
||||||
@@ -451,7 +192,7 @@ public struct NewFeatureView: View {
|
|||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.strokeBorder(Color("mode").opacity(0.2), lineWidth: 0.6)
|
.strokeBorder(.primary.opacity(0.2), lineWidth: 0.6)
|
||||||
)
|
)
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
@@ -459,7 +200,7 @@ public struct NewFeatureView: View {
|
|||||||
|
|
||||||
|
|
||||||
public struct SlideableDivider: View {
|
public struct SlideableDivider: View {
|
||||||
@EnvironmentObject private var themeSettings: ThemeSettings
|
@EnvironmentObject private var themeManager: ThemeManager
|
||||||
@Binding var dimension: Double
|
@Binding var dimension: Double
|
||||||
@State private var dimensionStart: Double?
|
@State private var dimensionStart: Double?
|
||||||
@State private var handleWidth: Double = 4
|
@State private var handleWidth: Double = 4
|
||||||
@@ -471,7 +212,7 @@ public struct SlideableDivider: View {
|
|||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundStyle(Color("mode").opacity(0.0))
|
.foregroundStyle(.primary.opacity(0.0))
|
||||||
.frame(width: 10)
|
.frame(width: 10)
|
||||||
.onHover { inside in
|
.onHover { inside in
|
||||||
if inside {
|
if inside {
|
||||||
@@ -518,117 +259,6 @@ public struct SlideableDivider: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct LabeledDivider: View {
|
|
||||||
let label: String
|
|
||||||
@EnvironmentObject var appState: AppState
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Rectangle()
|
|
||||||
.frame(height: 1)
|
|
||||||
.foregroundColor(Color("mode").opacity(0.2))
|
|
||||||
|
|
||||||
Text(label)
|
|
||||||
.textCase(.uppercase)
|
|
||||||
.font(.title2)
|
|
||||||
.foregroundColor(Color("mode").opacity(0.6))
|
|
||||||
.padding(.horizontal, 10)
|
|
||||||
.frame(minWidth: 80)
|
|
||||||
|
|
||||||
Rectangle()
|
|
||||||
.frame(height: 1)
|
|
||||||
.foregroundColor(Color("mode").opacity(0.2))
|
|
||||||
}
|
|
||||||
.frame(minHeight: 35)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct AnimatedSearchStyle: TextFieldStyle {
|
|
||||||
@State private var isHovered = false
|
|
||||||
@FocusState private var isFocused: Bool
|
|
||||||
@Binding var text: String
|
|
||||||
@EnvironmentObject var appState: AppState
|
|
||||||
@EnvironmentObject var fsm: FolderSettingsManager
|
|
||||||
|
|
||||||
func _body(configuration: TextField<Self._Label>) -> some View {
|
|
||||||
|
|
||||||
HStack(spacing: 5) {
|
|
||||||
|
|
||||||
if isHovered || !text.isEmpty {
|
|
||||||
|
|
||||||
configuration
|
|
||||||
.frame(height: 15)
|
|
||||||
.font(.title3)
|
|
||||||
.textFieldStyle(PlainTextFieldStyle())
|
|
||||||
.padding(.leading, 50)
|
|
||||||
|
|
||||||
|
|
||||||
Image(systemName: "arrow.triangle.2.circlepath")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.frame(width: 15, height: 15)
|
|
||||||
.padding(.trailing, 5)
|
|
||||||
.foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5))
|
|
||||||
.onTapGesture {
|
|
||||||
withAnimation {
|
|
||||||
reloadAppsList(appState: appState, fsm: fsm)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.help("Refresh app list")
|
|
||||||
|
|
||||||
|
|
||||||
if text != "" {
|
|
||||||
Image(systemName: "xmark")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.frame(width: 10, height: 10)
|
|
||||||
.padding(.trailing, 5)
|
|
||||||
.foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5))
|
|
||||||
.onTapGesture {
|
|
||||||
withAnimation {
|
|
||||||
text = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.help("Clear search")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "magnifyingglass")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 18, height: 18)
|
|
||||||
.foregroundColor(isHovered ? Color("mode") : Color("mode").opacity(0.5))
|
|
||||||
}
|
|
||||||
.frame(width: 150)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(8)
|
|
||||||
.overlay(
|
|
||||||
Group {
|
|
||||||
if isHovered || !text.isEmpty {
|
|
||||||
RoundedRectangle(cornerRadius: 6)
|
|
||||||
.strokeBorder(Color("mode").opacity(0.4), lineWidth: 1)
|
|
||||||
.allowsHitTesting(false)
|
|
||||||
.padding(.leading, 50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.onHover { hovering in
|
|
||||||
withAnimation(Animation.easeInOut(duration: 0.4)) {
|
|
||||||
self.isHovered = hovering
|
|
||||||
self.isFocused = hovering
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.focused($isFocused)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct RoundedTextFieldStyle: TextFieldStyle {
|
struct RoundedTextFieldStyle: TextFieldStyle {
|
||||||
func _body(configuration: TextField<Self._Label>) -> some View {
|
func _body(configuration: TextField<Self._Label>) -> some View {
|
||||||
@@ -638,7 +268,7 @@ struct RoundedTextFieldStyle: TextFieldStyle {
|
|||||||
.textFieldStyle(.plain)
|
.textFieldStyle(.plain)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.strokeBorder(Color("mode").opacity(0.4), lineWidth: 0.8)
|
.strokeBorder(.primary.opacity(0.4), lineWidth: 0.8)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -661,54 +291,6 @@ struct GlassEffect: NSViewRepresentable {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct VerticalDivider: View {
|
|
||||||
|
|
||||||
var color: Color = Color("AccentColor")
|
|
||||||
var width: CGFloat = 1
|
|
||||||
var height: CGFloat = 20
|
|
||||||
var opacity: Double = 1
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Divider()
|
|
||||||
.foregroundColor(color)
|
|
||||||
.frame(width: width, height: height)
|
|
||||||
.opacity(opacity)
|
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct WindowActionButton: ButtonStyle {
|
|
||||||
enum UserAction {
|
|
||||||
case accept
|
|
||||||
case cancel
|
|
||||||
}
|
|
||||||
@State private var hovered = false
|
|
||||||
let action: UserAction
|
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
|
||||||
configuration.label
|
|
||||||
.frame(width: 100)
|
|
||||||
.padding(10)
|
|
||||||
.background(
|
|
||||||
!configuration.isPressed ?
|
|
||||||
!hovered ?
|
|
||||||
action == .accept ? Color("button") : Color.red :
|
|
||||||
action == .accept ? Color("button").opacity(0.8) : Color.red.opacity(0.8) :
|
|
||||||
action == .accept ? Color("button").opacity(0.5) : Color.red.opacity(0.5)
|
|
||||||
)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.cornerRadius(8)
|
|
||||||
.onHover { hovering in
|
|
||||||
withAnimation(Animation.easeIn(duration: 0.15)) {
|
|
||||||
hovered = hovering
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct SimpleButtonBrightStyle: ButtonStyle {
|
struct SimpleButtonBrightStyle: ButtonStyle {
|
||||||
@State private var hovered = false
|
@State private var hovered = false
|
||||||
@@ -748,165 +330,6 @@ struct SimpleButtonBrightStyle: ButtonStyle {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct FilesViewActionButton: ButtonStyle {
|
|
||||||
enum UserAction {
|
|
||||||
case uninstall
|
|
||||||
case close
|
|
||||||
}
|
|
||||||
@State private var hovered = false
|
|
||||||
let action: UserAction
|
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
|
||||||
configuration.label
|
|
||||||
.frame(width: 70)
|
|
||||||
.padding(6)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 6)
|
|
||||||
.strokeBorder(
|
|
||||||
configuration.isPressed ?
|
|
||||||
Color("AccentColor").opacity(0.7) :
|
|
||||||
hovered ? Color("AccentColor").opacity(0.8) : Color("AccentColor").opacity(0.9), lineWidth: 2
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
)
|
|
||||||
.foregroundColor(
|
|
||||||
action == .uninstall ? Color("AccentColor") : Color("mode")
|
|
||||||
)
|
|
||||||
.cornerRadius(6)
|
|
||||||
.onHover { hovering in
|
|
||||||
withAnimation(Animation.easeIn(duration: 0.15)) {
|
|
||||||
hovered = hovering
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct SentinelToggleStyle: ToggleStyle {
|
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
ZStack{
|
|
||||||
RoundedRectangle(cornerRadius: 50)
|
|
||||||
.fill(configuration.isOn ? greenBG : redBG)
|
|
||||||
.frame(width: 70, height: 40)
|
|
||||||
HStack{
|
|
||||||
Image(systemName: "lock.shield")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 26, height: 26)
|
|
||||||
.padding(.leading, 10)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "shield")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 26, height: 26)
|
|
||||||
.padding(.trailing, 10)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.frame(width: 70, height: 40, alignment: .center)
|
|
||||||
.overlay(
|
|
||||||
Circle()
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.shadow(color: .black.opacity(0.5), radius: 3, x: 0, y: 0)
|
|
||||||
.padding(.all, 4)
|
|
||||||
.offset(x: configuration.isOn ? 15 : -15, y: 0)
|
|
||||||
)
|
|
||||||
.overlay(
|
|
||||||
Image(systemName: "power")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.foregroundColor(.black.opacity(0.3))
|
|
||||||
.offset(x: configuration.isOn ? 15 : -15, y: 0)
|
|
||||||
)
|
|
||||||
.onTapGesture {
|
|
||||||
withAnimation(.easeInOut(duration: 0.5)) {
|
|
||||||
configuration.isOn.toggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.animation(.default, value: configuration.isOn)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private let greenBG: AnyShapeStyle = AnyShapeStyle(
|
|
||||||
.green.shadow(.inner(radius: 2, x: 0, y: 1))
|
|
||||||
)
|
|
||||||
|
|
||||||
private let redBG: AnyShapeStyle = AnyShapeStyle(
|
|
||||||
.red.shadow(.inner(radius: 2, x: 0, y: 1))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct PillPicker: View {
|
|
||||||
@Binding var index: Int
|
|
||||||
var onTapAction: () -> Void
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .center, spacing: 10) {
|
|
||||||
ForEach(0..<3) { i in
|
|
||||||
ZStack {
|
|
||||||
Capsule()
|
|
||||||
.fill(Color.white)
|
|
||||||
.offset(x: (CGFloat(index - i) * 100))
|
|
||||||
.animation(.default, value: index)
|
|
||||||
.opacity(index == i ? 1 : 0)
|
|
||||||
.scaleEffect(index == i ? 1 : 0.70)
|
|
||||||
.shadow(color: Color.black.opacity(0.2), radius: 2, x: 0, y: 2)
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
|
|
||||||
Text(i == 0 ? "Apps" : (i == 1 ? "Widgets" : "Plugins"))
|
|
||||||
.font(.system(size: 12))
|
|
||||||
.foregroundColor(index == i ? (colorScheme == .dark ? .black : Color("AccentColor")) : Color("mode"))
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.frame(height: 25)
|
|
||||||
.background(.white.opacity(0.0001))
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
withAnimation {
|
|
||||||
index = i
|
|
||||||
onTapAction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(height: 25)
|
|
||||||
.padding(5)
|
|
||||||
.background(
|
|
||||||
Capsule()
|
|
||||||
.fill(colorScheme == .dark ? .white.opacity(0.1) : .black.opacity(0.06))
|
|
||||||
.overlay(
|
|
||||||
Capsule()
|
|
||||||
.stroke(Color.black.opacity(colorScheme == .dark ? 0.8 : 0.2), lineWidth: 1)
|
|
||||||
.blur(radius: 1)
|
|
||||||
.offset(y: 1)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.clipShape(Capsule())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct PearDropView: View {
|
struct PearDropView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -914,9 +337,9 @@ struct PearDropView: View {
|
|||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
LinearGradient(gradient: Gradient(colors: [.green, .orange]), startPoint: .leading, endPoint: .trailing)
|
LinearGradient(gradient: Gradient(colors: [.green, .orange]), startPoint: .leading, endPoint: .trailing)
|
||||||
.frame(width: 220)
|
.frame(width: 220)
|
||||||
LinearGradient(gradient: Gradient(colors: [.orange, Color("mode").opacity(0.5)]), startPoint: .leading, endPoint: .trailing)
|
LinearGradient(gradient: Gradient(colors: [.orange, .primary.opacity(0.5)]), startPoint: .leading, endPoint: .trailing)
|
||||||
.frame(width: 10)
|
.frame(width: 10)
|
||||||
LinearGradient(gradient: Gradient(colors: [Color("mode").opacity(0.5), Color("mode").opacity(0.5)]), startPoint: .leading, endPoint: .trailing)
|
LinearGradient(gradient: Gradient(colors: [.primary.opacity(0.5), .primary.opacity(0.5)]), startPoint: .leading, endPoint: .trailing)
|
||||||
.frame(width: 300)
|
.frame(width: 300)
|
||||||
}
|
}
|
||||||
.mask(
|
.mask(
|
||||||
@@ -934,71 +357,14 @@ struct PearDropView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Used for testing UI bounds
|
|
||||||
extension View {
|
|
||||||
func bounds() -> some View {
|
|
||||||
self.border(Color.red, width: 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background color/glass setter
|
// Background color/glass setter
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func backgroundView(themeSettings: ThemeSettings, darker: Bool = false, glass: Bool = false) -> some View {
|
func backgroundView(themeManager: ThemeManager = ThemeManager.shared, darker: Bool = false, glass: Bool = false) -> some View {
|
||||||
if glass {
|
if glass {
|
||||||
GlassEffect(material: .sidebar, blendingMode: .behindWindow)
|
GlassEffect(material: .sidebar, blendingMode: .behindWindow)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
} else {
|
} else {
|
||||||
darker ? themeSettings.themeColor.darker(by: 5).edgesIgnoringSafeArea(.all) : themeSettings.themeColor.edgesIgnoringSafeArea(.all)
|
darker ? themeManager.pickerColor.adjustBrightness(5).edgesIgnoringSafeArea(.all) : themeManager.pickerColor.edgesIgnoringSafeArea(.all)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
|
||||||
HStack(alignment: .center, spacing: 10) {
|
|
||||||
Circle()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.fixedSize(horizontal: true, vertical: true)
|
|
||||||
.foregroundColor(fillColor)
|
|
||||||
.background(fillColor)
|
|
||||||
.clipShape(Circle())
|
|
||||||
.help(label)
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: 25)
|
|
||||||
.strokeBorder(Color("mode").opacity(0.8), lineWidth: 1))
|
|
||||||
// Text(label)
|
|
||||||
}
|
|
||||||
.onHover { inside in inside ? NSCursor.pointingHand.push() : NSCursor.pop() }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
//
|
|
||||||
// ThemeManager.swift
|
|
||||||
// Pearcleaner
|
|
||||||
//
|
|
||||||
// Created by Alin Lupascu on 4/12/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
import AppKit
|
|
||||||
|
|
||||||
class ThemeSettings: ObservableObject {
|
|
||||||
static let shared = ThemeSettings()
|
|
||||||
private let userDefaults = UserDefaults.standard
|
|
||||||
private let colorKey = "themeColor"
|
|
||||||
|
|
||||||
@Published var themeColor: Color {
|
|
||||||
didSet {
|
|
||||||
saveThemeColor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// light - Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1)
|
|
||||||
// dark - Color(.sRGB, red: 0.149, green: 0.149, blue: 0.149, opacity: 1)
|
|
||||||
// pearcleaner - Color(.sRGB, red: 0.188143, green: 0.208556, blue: 0.262679, opacity: 1)
|
|
||||||
|
|
||||||
init() {
|
|
||||||
themeColor = userDefaults.color(forKey: colorKey) ?? .clear
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupInitialColor(forcedDarkMode: Bool? = nil) {
|
|
||||||
// Determine dark mode either from forced setting or system setting
|
|
||||||
let darkMode = forcedDarkMode ?? isDarkMode()
|
|
||||||
|
|
||||||
// Set the theme color based on the dark mode status
|
|
||||||
themeColor = darkMode ? Color(.sRGB, red: 0.188143, green: 0.208556, blue: 0.262679, opacity: 1) :
|
|
||||||
Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1)
|
|
||||||
saveThemeColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func loadThemeColor() {
|
|
||||||
if let components = UserDefaults.standard.array(forKey: colorKey) as? [CGFloat], components.count == 4 {
|
|
||||||
themeColor = Color(.sRGB, red: components[0], green: components[1], blue: components[2], opacity: components[3])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveThemeColor() {
|
|
||||||
let nsColor = NSColor(themeColor)
|
|
||||||
if let components = nsColor.cgColor.components {
|
|
||||||
UserDefaults.standard.set(components, forKey: colorKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetToDefault(dark: Bool = true) {
|
|
||||||
themeColor = dark ? Color(.sRGB, red: 0.188143, green: 0.208556, blue: 0.262679, opacity: 1) : Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1)
|
|
||||||
// themeColor = dark ? Color(.sRGB, red: 0.149, green: 0.149, blue: 0.149, opacity: 1) : Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1)
|
|
||||||
saveThemeColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setPreset(preset: String, colorScheme: DisplayMode) {
|
|
||||||
themeColor = getColorForPreset(preset: preset, colorScheme: colorScheme)
|
|
||||||
saveThemeColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getColorForPreset(preset: String, colorScheme: DisplayMode) -> Color {
|
|
||||||
let pearcleaner = colorScheme.colorScheme == .light ? Color(.sRGB, red: 0.499549, green: 0.545169, blue: 0.682028, opacity: 1) : Color(.sRGB, red: 0.188143, green: 0.208556, blue: 0.262679, opacity: 1)
|
|
||||||
let solarized = colorScheme.colorScheme == .light ? Color(.sRGB, red: 0.554372, green: 0.6557, blue: 0.734336, opacity: 1) : Color(.sRGB, red: 0.117257, green: 0.22506, blue: 0.249171, opacity: 1)
|
|
||||||
let dracula = colorScheme.colorScheme == .light ? Color(.sRGB, red: 0.567094, green: 0.562125, blue: 0.81285, opacity: 1) : Color(.sRGB, red: 0.268614, green: 0.264737, blue: 0.383503, opacity: 1)
|
|
||||||
let macOS = colorScheme.colorScheme == .light ? Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1) : Color(.sRGB, red: 0.149, green: 0.149, blue: 0.149, opacity: 1)
|
|
||||||
|
|
||||||
switch preset {
|
|
||||||
case "pearcleaner":
|
|
||||||
return pearcleaner
|
|
||||||
case "solarized":
|
|
||||||
return solarized
|
|
||||||
case "dracula":
|
|
||||||
return dracula
|
|
||||||
case "macOS":
|
|
||||||
return macOS
|
|
||||||
default:
|
|
||||||
return themeColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Color {
|
|
||||||
/// Darkens the color by a percentage.
|
|
||||||
func darker(by percentage: CGFloat = 30.0) -> Color {
|
|
||||||
var hsb = (hue: CGFloat(0), saturation: CGFloat(0), brightness: CGFloat(0), alpha: CGFloat(0))
|
|
||||||
NSColor(self).getHue(&hsb.hue, saturation: &hsb.saturation, brightness: &hsb.brightness, alpha: &hsb.alpha)
|
|
||||||
return Color(hue: hsb.hue, saturation: hsb.saturation, brightness: max(hsb.brightness - percentage/100, 0), opacity: hsb.alpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lightens the color by a percentage.
|
|
||||||
func lighter(by percentage: CGFloat = 30.0) -> Color {
|
|
||||||
var hsb = (hue: CGFloat(0), saturation: CGFloat(0), brightness: CGFloat(0), alpha: CGFloat(0))
|
|
||||||
NSColor(self).getHue(&hsb.hue, saturation: &hsb.saturation, brightness: &hsb.brightness, alpha: &hsb.alpha)
|
|
||||||
return Color(hue: hsb.hue, saturation: hsb.saturation, brightness: min(hsb.brightness + percentage / 100, 1.0), opacity: hsb.alpha)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Color {
|
|
||||||
/// Checks if the current themeColor is of a light or dark variant to set the appropriate displayMode on launch
|
|
||||||
func isLight() -> Bool? {
|
|
||||||
guard let components = NSColor(self).cgColor.components, components.count >= 3 else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Formula to calculate perceived luminance
|
|
||||||
let brightness = 0.299 * components[0] + 0.587 * components[1] + 0.114 * components[2]
|
|
||||||
return brightness > 0.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UserDefaults {
|
|
||||||
func color(forKey key: String) -> Color? {
|
|
||||||
guard let components = array(forKey: key) as? [CGFloat], components.count == 4 else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return Color(.sRGB, red: components[0], green: components[1], blue: components[2], opacity: components[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
func setColor(_ color: Color, forKey key: String) {
|
|
||||||
let nsColor = NSColor(color)
|
|
||||||
if let components = nsColor.cgColor.components, components.count == 4 {
|
|
||||||
set(components, forKey: key)
|
|
||||||
} else {
|
|
||||||
removeObject(forKey: key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
//
|
|
||||||
// Updater.swift
|
|
||||||
// Pearcleaner
|
|
||||||
//
|
|
||||||
// Created by Alin Lupascu on 5/3/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
|
|
||||||
struct Release: Codable {
|
|
||||||
let id: Int
|
|
||||||
let tag_name: String
|
|
||||||
let body: String
|
|
||||||
let assets: [Asset]
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Asset: Codable {
|
|
||||||
let name: String
|
|
||||||
let url: String
|
|
||||||
let browser_download_url: String
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Release {
|
|
||||||
var modifiedBody: String {
|
|
||||||
return body.replacingOccurrences(of: "- [x]", with: ">").replacingOccurrences(of: "###", with: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// --- Updater functionality
|
|
||||||
|
|
||||||
|
|
||||||
func loadGithubReleases(appState: AppState, manual: Bool = false, releaseOnly: Bool = false) {
|
|
||||||
let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/releases")!
|
|
||||||
let request = URLRequest(url: url)
|
|
||||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
||||||
if let data = data {
|
|
||||||
if let decodedResponse = try? JSONDecoder().decode([Release].self, from: data) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let lastFewReleases = Array(decodedResponse.prefix(3)) // Get only the last 3 recent releases
|
|
||||||
appState.releases = lastFewReleases
|
|
||||||
if !releaseOnly {
|
|
||||||
checkForUpdate(appState: appState, manual: manual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printOS("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
|
|
||||||
}.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkForUpdate(appState: AppState, manual: Bool) {
|
|
||||||
guard let latestRelease = appState.releases.first else { return }
|
|
||||||
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
|
||||||
if latestRelease.tag_name > currentVersion {
|
|
||||||
updateOnMain {
|
|
||||||
appState.updateAvailable = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if manual {
|
|
||||||
NewWin.show(appState: appState, width: 500, height: 300, newWin: .no_update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func downloadUpdate(appState: AppState) {
|
|
||||||
updateOnMain {
|
|
||||||
appState.progressBar.0 = "UPDATER: Getting update link"
|
|
||||||
appState.progressBar.1 = 0.1
|
|
||||||
}
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
guard let latestRelease = appState.releases.first else { return }
|
|
||||||
guard let asset = latestRelease.assets.first else { return }
|
|
||||||
guard let url = URL(string: asset.url) else { return }
|
|
||||||
var request = URLRequest(url: url)
|
|
||||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Accept")
|
|
||||||
|
|
||||||
let downloadTask = URLSession.shared.downloadTask(with: request) { localURL, urlResponse, error in
|
|
||||||
updateOnMain {
|
|
||||||
appState.progressBar.0 = "UPDATER: Starting download of update file"
|
|
||||||
appState.progressBar.1 = 0.2
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let localURL = localURL else { return }
|
|
||||||
|
|
||||||
let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(asset.name)")
|
|
||||||
|
|
||||||
do {
|
|
||||||
if fileManager.fileExists(atPath: destinationURL.path) {
|
|
||||||
try? fileManager.removeItem(at: destinationURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOnMain {
|
|
||||||
appState.progressBar.0 = "UPDATER: File downloaded to temp directory"
|
|
||||||
appState.progressBar.1 = 0.3
|
|
||||||
}
|
|
||||||
try fileManager.moveItem(at: localURL, to: destinationURL)
|
|
||||||
|
|
||||||
updateOnMain {
|
|
||||||
appState.progressBar.0 = "UPDATER: File renamed using asset name"
|
|
||||||
appState.progressBar.1 = 0.4
|
|
||||||
}
|
|
||||||
|
|
||||||
UnzipAndReplace(DownloadedFileURL: destinationURL.path, appState: appState)
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
printOS("Error moving downloaded file: \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadTask.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnzipAndReplace(DownloadedFileURL fileURL: String, appState: AppState) {
|
|
||||||
let appDirectory = Bundle.main.bundleURL.deletingLastPathComponent().path
|
|
||||||
let appBundle = Bundle.main.bundleURL.path
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
|
|
||||||
do {
|
|
||||||
updateOnMain {
|
|
||||||
appState.progressBar.0 = "UPDATER: Removing currently installed application bundle"
|
|
||||||
appState.progressBar.1 = 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the old version of your app
|
|
||||||
try fileManager.removeItem(atPath: appBundle)
|
|
||||||
|
|
||||||
updateOnMain {
|
|
||||||
appState.progressBar.0 = "UPDATER: Unziping file to original install location"
|
|
||||||
appState.progressBar.1 = 0.6
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unzip the downloaded update file to your app's bundle path
|
|
||||||
let process = Process()
|
|
||||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/ditto")
|
|
||||||
process.arguments = ["-xk", fileURL, appDirectory]
|
|
||||||
process.standardOutput = FileHandle.nullDevice
|
|
||||||
process.standardError = FileHandle.nullDevice
|
|
||||||
|
|
||||||
try process.run()
|
|
||||||
process.waitUntilExit()
|
|
||||||
|
|
||||||
updateOnMain {
|
|
||||||
appState.progressBar.0 = "UPDATER: Removing file from temp directory"
|
|
||||||
appState.progressBar.1 = 0.8
|
|
||||||
}
|
|
||||||
|
|
||||||
// After unzipping, remove the update file
|
|
||||||
try fileManager.removeItem(atPath: fileURL)
|
|
||||||
|
|
||||||
updateOnMain {
|
|
||||||
appState.progressBar.0 = "UPDATER: Completed, please restart!"
|
|
||||||
appState.progressBar.1 = 1.0
|
|
||||||
appState.updateAvailable = false
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
printOS("Error updating the app: \(error)")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- Updater check frequency
|
|
||||||
|
|
||||||
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 checkAndUpdateIfNeeded(appState: AppState) {
|
|
||||||
@AppStorage("settings.updater.updateFrequency") var updateFrequency: UpdateFrequency = .daily
|
|
||||||
@AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
|
||||||
|
|
||||||
guard let updateInterval = updateFrequency.interval else {
|
|
||||||
printOS("Updater: no update frequency set, skipping check")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let now = Date()
|
|
||||||
let nextUpdateDateLocal = Date(timeIntervalSinceReferenceDate: nextUpdateDate)
|
|
||||||
|
|
||||||
if !isSameDay(date1: nextUpdateDateLocal, date2: now) {
|
|
||||||
printOS("Updater: next update date is in the future, skipping (\(nextUpdateDateLocal))")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
updateApp(appState: appState)
|
|
||||||
setNextUpdateDate(interval: updateInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateApp(appState: AppState) {
|
|
||||||
// Perform your update logic here
|
|
||||||
printOS("Updater: checking for 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")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSameDay(date1: Date, date2: Date) -> Bool {
|
|
||||||
return Calendar.current.isDate(date1, inSameDayAs: date2)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// --- Updater Badge View
|
|
||||||
|
|
||||||
|
|
||||||
struct UpdateNotificationView: View {
|
|
||||||
let appState: AppState
|
|
||||||
@State private var hovered: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
Text("Update Available!")
|
|
||||||
.font(.callout)
|
|
||||||
.opacity(0.5)
|
|
||||||
.padding(.leading, 7)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
Image(systemName: !hovered ? "square.and.arrow.down" : "square.and.arrow.down.fill")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 14, height: 14)
|
|
||||||
.animation(.easeInOut(duration: 0.2), value: hovered)
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
|
|
||||||
|
|
||||||
Text("v\(appState.releases.first?.tag_name ?? "Update")")
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(3)
|
|
||||||
.onHover { hovering in
|
|
||||||
withAnimation() {
|
|
||||||
hovered = hovering
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onTapGesture {
|
|
||||||
NewWin.show(appState: appState, width: 500, height: 440, newWin: .update)
|
|
||||||
}
|
|
||||||
.help("Download latest update")
|
|
||||||
.padding(.horizontal, 5)
|
|
||||||
.padding(.vertical, 4)
|
|
||||||
.background(Color("pear"))
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
|
||||||
|
|
||||||
}
|
|
||||||
.frame(height: 30)
|
|
||||||
.padding(5)
|
|
||||||
.background(Color("mode").opacity(0.05))
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.bottom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+318
-319
@@ -7,30 +7,29 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import OSLog
|
import AlinFoundation
|
||||||
import CoreImage
|
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
// Make updates on main thread
|
// Make updates on main thread
|
||||||
func updateOnMain(after delay: Double? = nil, _ updates: @escaping () -> Void) {
|
//func updateOnMain(after delay: Double? = nil, _ updates: @escaping () -> Void) {
|
||||||
if let delay = delay {
|
// if let delay = delay {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
// DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||||
updates()
|
// updates()
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
DispatchQueue.main.async {
|
// DispatchQueue.main.async {
|
||||||
updates()
|
// updates()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
// Execute functions on background thread
|
//// Execute functions on background thread
|
||||||
func updateOnBackground(_ updates: @escaping () -> Void) {
|
//func updateOnBackground(_ updates: @escaping () -> Void) {
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
// DispatchQueue.global(qos: .userInitiated).async {
|
||||||
updates()
|
// updates()
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Reload apps list
|
// Reload apps list
|
||||||
func reloadAppsList(appState: AppState, fsm: FolderSettingsManager) {
|
func reloadAppsList(appState: AppState, fsm: FolderSettingsManager) {
|
||||||
@@ -57,57 +56,57 @@ func resizeWindowAuto(windowSettings: WindowSettings, title: String) {
|
|||||||
|
|
||||||
|
|
||||||
// Check app directory based on user permission
|
// Check app directory based on user permission
|
||||||
func checkAppDirectoryAndUserRole(completion: @escaping ((isInCorrectDirectory: Bool, isAdmin: Bool)) -> Void) {
|
//func checkAppDirectoryAndUserRole(completion: @escaping ((isInCorrectDirectory: Bool, isAdmin: Bool)) -> Void) {
|
||||||
isCurrentUserAdmin { isAdmin in
|
// isCurrentUserAdmin { isAdmin in
|
||||||
let bundlePath = Bundle.main.bundlePath as NSString
|
// let bundlePath = Bundle.main.bundlePath as NSString
|
||||||
let applicationsDir = "/Applications"
|
// let applicationsDir = "/Applications"
|
||||||
let userApplicationsDir = "\(home)/Applications"
|
// let userApplicationsDir = "\(home)/Applications"
|
||||||
|
//
|
||||||
var isInCorrectDirectory = false
|
// var isInCorrectDirectory = false
|
||||||
|
//
|
||||||
if isAdmin {
|
// if isAdmin {
|
||||||
// Admins can have the app in either /Applications or ~/Applications
|
// // Admins can have the app in either /Applications or ~/Applications
|
||||||
isInCorrectDirectory = bundlePath.deletingLastPathComponent == applicationsDir ||
|
// isInCorrectDirectory = bundlePath.deletingLastPathComponent == applicationsDir ||
|
||||||
bundlePath.deletingLastPathComponent == userApplicationsDir
|
// bundlePath.deletingLastPathComponent == userApplicationsDir
|
||||||
} else {
|
// } else {
|
||||||
// Standard users should only have the app in ~/Applications
|
// // Standard users should only have the app in ~/Applications
|
||||||
isInCorrectDirectory = bundlePath.deletingLastPathComponent == userApplicationsDir
|
// isInCorrectDirectory = bundlePath.deletingLastPathComponent == userApplicationsDir
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Return both conditions: if the app is in the correct directory and if the user is an admin
|
// // Return both conditions: if the app is in the correct directory and if the user is an admin
|
||||||
completion((isInCorrectDirectory, isAdmin))
|
// completion((isInCorrectDirectory, isAdmin))
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
// Check if user is admin or standard user
|
//// Check if user is admin or standard user
|
||||||
func isCurrentUserAdmin(completion: @escaping (Bool) -> Void) {
|
//func isCurrentUserAdmin(completion: @escaping (Bool) -> Void) {
|
||||||
let process = Process()
|
// let process = Process()
|
||||||
process.executableURL = URL(fileURLWithPath: "/bin/zsh") // Using zsh, macOS default shell
|
// process.executableURL = URL(fileURLWithPath: "/bin/zsh") // Using zsh, macOS default shell
|
||||||
process.arguments = ["-c", "groups $(whoami) | grep -q ' admin '"]
|
// process.arguments = ["-c", "groups $(whoami) | grep -q ' admin '"]
|
||||||
|
//
|
||||||
process.terminationHandler = { process in
|
// process.terminationHandler = { process in
|
||||||
// On macOS, a process's exit status of 0 indicates success (admin group found in this context)
|
// // On macOS, a process's exit status of 0 indicates success (admin group found in this context)
|
||||||
completion(process.terminationStatus == 0)
|
// completion(process.terminationStatus == 0)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
do {
|
// do {
|
||||||
try process.run()
|
// try process.run()
|
||||||
} catch {
|
// } catch {
|
||||||
print("Failed to execute command: \(error)")
|
// print("Failed to execute command: \(error)")
|
||||||
completion(false)
|
// completion(false)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Check if appearance is dark mode
|
// Check if appearance is dark mode
|
||||||
func isDarkMode() -> Bool {
|
//func isDarkMode() -> Bool {
|
||||||
return NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
|
// return NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// Set app color mode
|
//// Set app color mode
|
||||||
func setAppearance(mode: NSAppearance.Name) {
|
//func setAppearance(mode: NSAppearance.Name) {
|
||||||
NSApp.appearance = NSAppearance(named: mode)
|
// NSApp.appearance = NSAppearance(named: mode)
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Check if Pearcleaner has any windows open
|
// Check if Pearcleaner has any windows open
|
||||||
func hasWindowOpen() -> Bool {
|
func hasWindowOpen() -> Bool {
|
||||||
@@ -118,13 +117,13 @@ func hasWindowOpen() -> Bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find and hide/show main app window when using menubar item
|
// Find and hide/show main app window when using menubar item
|
||||||
func findAndHideWindows(named titles: [String]) {
|
//func findAndHideWindows(named titles: [String]) {
|
||||||
for title in titles {
|
// for title in titles {
|
||||||
if let window = NSApp.windows.first(where: { $0.title == title }) {
|
// if let window = NSApp.windows.first(where: { $0.title == title }) {
|
||||||
window.close()
|
// window.close()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
func findAndSetWindowFrame(named titles: [String], windowSettings: WindowSettings) {
|
func findAndSetWindowFrame(named titles: [String], windowSettings: WindowSettings) {
|
||||||
windowSettings.registerDefaultWindowSettings() {
|
windowSettings.registerDefaultWindowSettings() {
|
||||||
@@ -139,20 +138,20 @@ func findAndSetWindowFrame(named titles: [String], windowSettings: WindowSetting
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func findAndShowWindows(named titles: [String]) {
|
//func findAndShowWindows(named titles: [String]) {
|
||||||
for title in titles {
|
// for title in titles {
|
||||||
if let window = NSApp.windows.first(where: { $0.title == title }) {
|
// if let window = NSApp.windows.first(where: { $0.title == title }) {
|
||||||
window.makeKeyAndOrderFront(nil)
|
// window.makeKeyAndOrderFront(nil)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Copy to clipboard
|
// Copy to clipboard
|
||||||
func copyToClipboard(text: String) {
|
//func copyToClipboard(text: String) {
|
||||||
let pasteboard = NSPasteboard.general
|
// let pasteboard = NSPasteboard.general
|
||||||
pasteboard.clearContents()
|
// pasteboard.clearContents()
|
||||||
pasteboard.setString(text, forType: .string)
|
// pasteboard.setString(text, forType: .string)
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -224,15 +223,15 @@ func saveURLsToFile(urls: Set<URL>, appState: AppState) {
|
|||||||
|
|
||||||
|
|
||||||
// Check if symlink
|
// Check if symlink
|
||||||
func isSymlink(atPath path: URL) -> Bool {
|
//func isSymlink(atPath path: URL) -> Bool {
|
||||||
do {
|
// do {
|
||||||
let _ = try path.checkResourceIsReachable()
|
// let _ = try path.checkResourceIsReachable()
|
||||||
let resourceValues = try path.resourceValues(forKeys: [.isSymbolicLinkKey])
|
// let resourceValues = try path.resourceValues(forKeys: [.isSymbolicLinkKey])
|
||||||
return resourceValues.isSymbolicLink == true
|
// return resourceValues.isSymbolicLink == true
|
||||||
} catch {
|
// } catch {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// Open trash folder
|
// Open trash folder
|
||||||
@@ -297,62 +296,62 @@ func checkAppBundleArchitecture(at appBundlePath: String) -> Arch {
|
|||||||
|
|
||||||
|
|
||||||
// Convert icon to png so colors render correctly
|
// Convert icon to png so colors render correctly
|
||||||
func convertICNSToPNG(icon: NSImage, size: NSSize) -> NSImage? {
|
//func convertICNSToPNG(icon: NSImage, size: NSSize) -> NSImage? {
|
||||||
// Resize the icon to the specified size
|
// // Resize the icon to the specified size
|
||||||
let resizedIcon = NSImage(size: size)
|
// let resizedIcon = NSImage(size: size)
|
||||||
resizedIcon.lockFocus()
|
// resizedIcon.lockFocus()
|
||||||
icon.draw(in: NSRect(x: 0, y: 0, width: size.width, height: size.height))
|
// icon.draw(in: NSRect(x: 0, y: 0, width: size.width, height: size.height))
|
||||||
resizedIcon.unlockFocus()
|
// resizedIcon.unlockFocus()
|
||||||
|
//
|
||||||
// Convert the resized icon to PNG format
|
// // Convert the resized icon to PNG format
|
||||||
if let resizedImageData = resizedIcon.tiffRepresentation,
|
// if let resizedImageData = resizedIcon.tiffRepresentation,
|
||||||
let resizedBitmap = NSBitmapImageRep(data: resizedImageData),
|
// let resizedBitmap = NSBitmapImageRep(data: resizedImageData),
|
||||||
let pngData = resizedBitmap.representation(using: .png, properties: [:]) {
|
// let pngData = resizedBitmap.representation(using: .png, properties: [:]) {
|
||||||
return NSImage(data: pngData)
|
// return NSImage(data: pngData)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return nil
|
// return nil
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Get icon for files and folders
|
// Get icon for files and folders
|
||||||
func getIconForFileOrFolder(atPath path: URL) -> Image? {
|
//func getIconForFileOrFolder(atPath path: URL) -> Image? {
|
||||||
return Image(nsImage: NSWorkspace.shared.icon(forFile: path.path))
|
// return Image(nsImage: NSWorkspace.shared.icon(forFile: path.path))
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func getIconForFileOrFolderNS(atPath path: URL) -> NSImage? {
|
//func getIconForFileOrFolderNS(atPath path: URL) -> NSImage? {
|
||||||
return NSWorkspace.shared.icon(forFile: path.path)
|
// return NSWorkspace.shared.icon(forFile: path.path)
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// Get average color from image
|
// Get average color from image
|
||||||
extension NSImage {
|
//extension NSImage {
|
||||||
var averageColor: NSColor? {
|
// var averageColor: NSColor? {
|
||||||
guard let tiffData = self.tiffRepresentation, let bitmapImage = NSBitmapImageRep(data: tiffData), let inputImage = CIImage(bitmapImageRep: bitmapImage) else { return nil }
|
// guard let tiffData = self.tiffRepresentation, let bitmapImage = NSBitmapImageRep(data: tiffData), let inputImage = CIImage(bitmapImageRep: bitmapImage) else { return nil }
|
||||||
|
//
|
||||||
let extentVector = CIVector(x: inputImage.extent.origin.x, y: inputImage.extent.origin.y, z: inputImage.extent.size.width, w: inputImage.extent.size.height)
|
// let extentVector = CIVector(x: inputImage.extent.origin.x, y: inputImage.extent.origin.y, z: inputImage.extent.size.width, w: inputImage.extent.size.height)
|
||||||
|
//
|
||||||
guard let filter = CIFilter(name: "CIAreaAverage", parameters: [kCIInputImageKey: inputImage, kCIInputExtentKey: extentVector]) else { return nil }
|
// guard let filter = CIFilter(name: "CIAreaAverage", parameters: [kCIInputImageKey: inputImage, kCIInputExtentKey: extentVector]) else { return nil }
|
||||||
guard let outputImage = filter.outputImage else { return nil }
|
// guard let outputImage = filter.outputImage else { return nil }
|
||||||
|
//
|
||||||
var bitmap = [UInt8](repeating: 0, count: 4)
|
// var bitmap = [UInt8](repeating: 0, count: 4)
|
||||||
let context = CIContext(options: [.workingColorSpace: NSNull()])
|
// let context = CIContext(options: [.workingColorSpace: NSNull()])
|
||||||
context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: CIFormat.RGBA8, colorSpace: nil)
|
// context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: CIFormat.RGBA8, colorSpace: nil)
|
||||||
|
//
|
||||||
return NSColor(red: CGFloat(bitmap[0]) / 255, green: CGFloat(bitmap[1]) / 255, blue: CGFloat(bitmap[2]) / 255, alpha: CGFloat(bitmap[3]) / 255)
|
// return NSColor(red: CGFloat(bitmap[0]) / 255, green: CGFloat(bitmap[1]) / 255, blue: CGFloat(bitmap[2]) / 255, alpha: CGFloat(bitmap[3]) / 255)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// Relaunch app
|
// Relaunch app
|
||||||
func relaunchApp(afterDelay seconds: TimeInterval = 0.5) -> Never {
|
//func relaunchApp(afterDelay seconds: TimeInterval = 0.5) -> Never {
|
||||||
let task = Process()
|
// let task = Process()
|
||||||
task.launchPath = "/bin/sh"
|
// task.launchPath = "/bin/sh"
|
||||||
task.arguments = ["-c", "sleep \(seconds); open \"\(Bundle.main.bundlePath)\""]
|
// task.arguments = ["-c", "sleep \(seconds); open \"\(Bundle.main.bundlePath)\""]
|
||||||
task.launch()
|
// task.launch()
|
||||||
|
//
|
||||||
NSApp.terminate(nil)
|
// NSApp.terminate(nil)
|
||||||
exit(0)
|
// exit(0)
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Check if app is running before deleting app files
|
// Check if app is running before deleting app files
|
||||||
func killApp(appId: String, completion: @escaping () -> Void = {}) {
|
func killApp(appId: String, completion: @escaping () -> Void = {}) {
|
||||||
@@ -416,7 +415,7 @@ func folderImages(for path: String) -> AnyView? {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 13)
|
.frame(width: 13)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.help("Container")
|
.help("Container")
|
||||||
)
|
)
|
||||||
} else if path.contains("/Library/Application Scripts/") {
|
} else if path.contains("/Library/Application Scripts/") {
|
||||||
@@ -425,7 +424,7 @@ func folderImages(for path: String) -> AnyView? {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 13)
|
.frame(width: 13)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.help("Application Script")
|
.help("Application Script")
|
||||||
)
|
)
|
||||||
} else if path.contains(".plist") {
|
} else if path.contains(".plist") {
|
||||||
@@ -434,7 +433,7 @@ func folderImages(for path: String) -> AnyView? {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 13)
|
.frame(width: 13)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.help("Plist File")
|
.help("Plist File")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -461,22 +460,22 @@ func isNested(path: URL) -> Bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if macOS is 14.0 or higher
|
// Check if macOS is 14.0 or higher
|
||||||
func isMacOS14OrHigher() -> Bool {
|
//func isMacOS14OrHigher() -> Bool {
|
||||||
if #available(macOS 14, *) {
|
// if #available(macOS 14, *) {
|
||||||
return true
|
// return true
|
||||||
} else {
|
// } else {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- Extend Int to convert hours to seconds ---
|
// --- Extend Int to convert hours to seconds ---
|
||||||
extension Int {
|
//extension Int {
|
||||||
var daysToSeconds: Double {
|
// var daysToSeconds: Double {
|
||||||
return Double(self) * 24 * 60 * 60
|
// return Double(self) * 24 * 60 * 60
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// --- Extend String to remove periods, spaces and lowercase the string
|
// --- Extend String to remove periods, spaces and lowercase the string
|
||||||
@@ -495,15 +494,15 @@ extension String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Capitalize first letter of string only
|
// --- Capitalize first letter of string only
|
||||||
extension String {
|
//extension String {
|
||||||
func capitalizingFirstLetter() -> String {
|
// func capitalizingFirstLetter() -> String {
|
||||||
return prefix(1).capitalized + dropFirst()
|
// return prefix(1).capitalized + dropFirst()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
mutating func capitalizeFirstLetter() {
|
// mutating func capitalizeFirstLetter() {
|
||||||
self = self.capitalizingFirstLetter()
|
// self = self.capitalizingFirstLetter()
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
// --- Returns comma separated string as array of strings
|
// --- Returns comma separated string as array of strings
|
||||||
extension String {
|
extension String {
|
||||||
@@ -517,18 +516,18 @@ extension String {
|
|||||||
|
|
||||||
|
|
||||||
// --- Overload the greater than operator ">" to do a semantic check on the string versions
|
// --- Overload the greater than operator ">" to do a semantic check on the string versions
|
||||||
extension String {
|
//extension String {
|
||||||
func versionStringToTuple() -> (Int, Int, Int) {
|
// func versionStringToTuple() -> (Int, Int, Int) {
|
||||||
let components = self.split(separator: ".").compactMap { Int($0) }
|
// let components = self.split(separator: ".").compactMap { Int($0) }
|
||||||
return (components[0], components[1], components[2])
|
// return (components[0], components[1], components[2])
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
static func > (lhs: String, rhs: String) -> Bool {
|
// static func > (lhs: String, rhs: String) -> Bool {
|
||||||
let lhsVersion = lhs.versionStringToTuple()
|
// let lhsVersion = lhs.versionStringToTuple()
|
||||||
let rhsVersion = rhs.versionStringToTuple()
|
// let rhsVersion = rhs.versionStringToTuple()
|
||||||
return lhsVersion > rhsVersion
|
// return lhsVersion > rhsVersion
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// --- Trash Relationship ---
|
// --- Trash Relationship ---
|
||||||
@@ -541,103 +540,103 @@ extension String {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
// --- Extend print command to also output to the Console ---
|
// --- Extend print command to also output to the Console ---
|
||||||
func printOS(_ items: Any..., separator: String = " ", terminator: String = "\n") {
|
//func printOS(_ items: Any..., separator: String = " ", terminator: String = "\n") {
|
||||||
let message = items.map { "\($0)" }.joined(separator: separator)
|
// let message = items.map { "\($0)" }.joined(separator: separator)
|
||||||
let log = OSLog(subsystem: "pearcleaner", category: "Application")
|
// let log = OSLog(subsystem: "pearcleaner", category: "Application")
|
||||||
os_log("%@", log: log, type: .default, message)
|
// os_log("%@", log: log, type: .default, message)
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// Get size of files
|
// Get size of files
|
||||||
func totalSizeOnDisk(for paths: [URL]) -> (real: Int64, logical: Int64) {
|
//func totalSizeOnDisk(for paths: [URL]) -> (real: Int64, logical: Int64) {
|
||||||
let fileManager = FileManager.default
|
// let fileManager = FileManager.default
|
||||||
var totalAllocatedSize: Int64 = 0
|
// var totalAllocatedSize: Int64 = 0
|
||||||
var totalFileSize: Int64 = 0
|
// var totalFileSize: Int64 = 0
|
||||||
|
//
|
||||||
for url in paths {
|
// for url in paths {
|
||||||
var isDirectory: ObjCBool = false
|
// var isDirectory: ObjCBool = false
|
||||||
if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) {
|
// if fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) {
|
||||||
let keys: [URLResourceKey] = [.totalFileAllocatedSizeKey, .fileSizeKey]
|
// let keys: [URLResourceKey] = [.totalFileAllocatedSizeKey, .fileSizeKey]
|
||||||
if isDirectory.boolValue {
|
// if isDirectory.boolValue {
|
||||||
// It's a directory, recurse into it
|
// // It's a directory, recurse into it
|
||||||
if let enumerator = fileManager.enumerator(at: url, includingPropertiesForKeys: keys, errorHandler: nil) {
|
// if let enumerator = fileManager.enumerator(at: url, includingPropertiesForKeys: keys, errorHandler: nil) {
|
||||||
for case let fileURL as URL in enumerator {
|
// for case let fileURL as URL in enumerator {
|
||||||
do {
|
// do {
|
||||||
let fileAttributes = try fileURL.resourceValues(forKeys: Set(keys))
|
// let fileAttributes = try fileURL.resourceValues(forKeys: Set(keys))
|
||||||
if let allocatedSize = fileAttributes.totalFileAllocatedSize {
|
// if let allocatedSize = fileAttributes.totalFileAllocatedSize {
|
||||||
totalAllocatedSize += Int64(allocatedSize)
|
// totalAllocatedSize += Int64(allocatedSize)
|
||||||
}
|
// }
|
||||||
if let fileSize = fileAttributes.fileSize {
|
// if let fileSize = fileAttributes.fileSize {
|
||||||
totalFileSize += Int64(fileSize)
|
// totalFileSize += Int64(fileSize)
|
||||||
}
|
// }
|
||||||
} catch {
|
// } catch {
|
||||||
print("Error getting file attributes for \(fileURL): \(error)")
|
// print("Error getting file attributes for \(fileURL): \(error)")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// It's a file
|
// // It's a file
|
||||||
do {
|
// do {
|
||||||
let fileAttributes = try url.resourceValues(forKeys: Set(keys))
|
// let fileAttributes = try url.resourceValues(forKeys: Set(keys))
|
||||||
if let allocatedSize = fileAttributes.totalFileAllocatedSize {
|
// if let allocatedSize = fileAttributes.totalFileAllocatedSize {
|
||||||
totalAllocatedSize += Int64(allocatedSize)
|
// totalAllocatedSize += Int64(allocatedSize)
|
||||||
}
|
// }
|
||||||
if let fileSize = fileAttributes.fileSize {
|
// if let fileSize = fileAttributes.fileSize {
|
||||||
totalFileSize += Int64(fileSize)
|
// totalFileSize += Int64(fileSize)
|
||||||
}
|
// }
|
||||||
} catch {
|
// } catch {
|
||||||
print("Error getting file attributes for \(url): \(error)")
|
// print("Error getting file attributes for \(url): \(error)")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return (real: totalAllocatedSize, logical: totalFileSize)
|
// return (real: totalAllocatedSize, logical: totalFileSize)
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
func totalSizeOnDisk(for path: URL) -> (real: Int64, logical: Int64) {
|
//func totalSizeOnDisk(for path: URL) -> (real: Int64, logical: Int64) {
|
||||||
return totalSizeOnDisk(for: [path])
|
// return totalSizeOnDisk(for: [path])
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// ByteFormatter
|
//// ByteFormatter
|
||||||
func formatByte(size: Int64) -> (human: String, byte: String) {
|
//func formatByte(size: Int64) -> (human: String, byte: String) {
|
||||||
let byteCountFormatter = ByteCountFormatter()
|
// let byteCountFormatter = ByteCountFormatter()
|
||||||
byteCountFormatter.countStyle = .file
|
// byteCountFormatter.countStyle = .file
|
||||||
byteCountFormatter.allowedUnits = [.useAll]
|
// byteCountFormatter.allowedUnits = [.useAll]
|
||||||
let human = byteCountFormatter.string(fromByteCount: size)
|
// let human = byteCountFormatter.string(fromByteCount: size)
|
||||||
|
//
|
||||||
let numberformatter = NumberFormatter()
|
// let numberformatter = NumberFormatter()
|
||||||
numberformatter.numberStyle = .decimal
|
// numberformatter.numberStyle = .decimal
|
||||||
let formattedNumber = numberformatter.string(from: NSNumber(value: size)) ?? "\(size)"
|
// let formattedNumber = numberformatter.string(from: NSNumber(value: size)) ?? "\(size)"
|
||||||
let byte = "\(formattedNumber)"
|
// let byte = "\(formattedNumber)"
|
||||||
|
//
|
||||||
return (human: human, byte: byte)
|
// return (human: human, byte: byte)
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// Only process supported files
|
// Only process supported files
|
||||||
func isSupportedFileType(at path: String) -> Bool {
|
//func isSupportedFileType(at path: String) -> Bool {
|
||||||
let fileManager = FileManager.default
|
// let fileManager = FileManager.default
|
||||||
do {
|
// do {
|
||||||
let attributes = try fileManager.attributesOfItem(atPath: path)
|
// let attributes = try fileManager.attributesOfItem(atPath: path)
|
||||||
if let fileType = attributes[FileAttributeKey.type] as? FileAttributeType {
|
// if let fileType = attributes[FileAttributeKey.type] as? FileAttributeType {
|
||||||
switch fileType {
|
// switch fileType {
|
||||||
case .typeRegular, .typeDirectory, .typeSymbolicLink:
|
// case .typeRegular, .typeDirectory, .typeSymbolicLink:
|
||||||
// The file is a regular file, directory, or symbolic link
|
// // The file is a regular file, directory, or symbolic link
|
||||||
return true
|
// return true
|
||||||
default:
|
// default:
|
||||||
// The file is a socket, pipe, or another type not supported
|
// // The file is a socket, pipe, or another type not supported
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} catch {
|
// } catch {
|
||||||
printOS("Error getting file attributes: \(error)")
|
// printOS("Error getting file attributes: \(error)")
|
||||||
}
|
// }
|
||||||
return false
|
// return false
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -671,43 +670,43 @@ func uninstallPearcleaner(appState: AppState, locations: Locations) {
|
|||||||
|
|
||||||
|
|
||||||
// --- Create Application Support folder if it doesn't exist ---
|
// --- Create Application Support folder if it doesn't exist ---
|
||||||
func ensureApplicationSupportFolderExists(appState: AppState) {
|
//func ensureApplicationSupportFolderExists(appState: AppState) {
|
||||||
let fileManager = FileManager.default
|
// let fileManager = FileManager.default
|
||||||
let supportURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("com.alienator88.Pearcleaner")
|
// let supportURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("com.alienator88.Pearcleaner")
|
||||||
|
//
|
||||||
// Check to make sure Application Support/Pearcleaner folder exists
|
// // Check to make sure Application Support/Pearcleaner folder exists
|
||||||
if !fileManager.fileExists(atPath: supportURL.path) {
|
// if !fileManager.fileExists(atPath: supportURL.path) {
|
||||||
try! fileManager.createDirectory(at: supportURL, withIntermediateDirectories: true)
|
// try! fileManager.createDirectory(at: supportURL, withIntermediateDirectories: true)
|
||||||
printOS("Created Application Support/com.alienator88.Pearcleaner folder")
|
// printOS("Created Application Support/com.alienator88.Pearcleaner folder")
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// --- Write Log to File for troubleshooting ---
|
// --- Write Log to File for troubleshooting ---
|
||||||
func writeLog(string: String) {
|
//func writeLog(string: String) {
|
||||||
let fileManager = FileManager.default
|
// let fileManager = FileManager.default
|
||||||
let home = fileManager.homeDirectoryForCurrentUser.path
|
// let home = fileManager.homeDirectoryForCurrentUser.path
|
||||||
let logFilePath = "\(home)/Downloads/log.txt"
|
// let logFilePath = "\(home)/Downloads/log.txt"
|
||||||
|
//
|
||||||
// Check if the log file exists, and create it if it doesn't
|
// // Check if the log file exists, and create it if it doesn't
|
||||||
if !fileManager.fileExists(atPath: logFilePath) {
|
// if !fileManager.fileExists(atPath: logFilePath) {
|
||||||
if !fileManager.createFile(atPath: logFilePath, contents: nil, attributes: nil) {
|
// if !fileManager.createFile(atPath: logFilePath, contents: nil, attributes: nil) {
|
||||||
printOS("Failed to create the log file.")
|
// printOS("Failed to create the log file.")
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
do {
|
// do {
|
||||||
if let fileHandle = FileHandle(forWritingAtPath: logFilePath) {
|
// if let fileHandle = FileHandle(forWritingAtPath: logFilePath) {
|
||||||
let ns = "\(string)\n"
|
// let ns = "\(string)\n"
|
||||||
fileHandle.seekToEndOfFile()
|
// fileHandle.seekToEndOfFile()
|
||||||
fileHandle.write(ns.data(using: .utf8)!)
|
// fileHandle.write(ns.data(using: .utf8)!)
|
||||||
fileHandle.closeFile()
|
// fileHandle.closeFile()
|
||||||
} else {
|
// } else {
|
||||||
printOS("Error opening file for appending")
|
// printOS("Error opening file for appending")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
// --- Load Plist file with launchctl ---
|
// --- Load Plist file with launchctl ---
|
||||||
@@ -752,20 +751,20 @@ func sendStopNotificationFW() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func getCurrentTimestamp() -> String {
|
//func getCurrentTimestamp() -> String {
|
||||||
let dateFormatter = DateFormatter()
|
// let dateFormatter = DateFormatter()
|
||||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
// dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
||||||
return dateFormatter.string(from: Date())
|
// return dateFormatter.string(from: Date())
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
func formattedDate(_ date: Date?) -> String {
|
//func formattedDate(_ date: Date?) -> String {
|
||||||
guard let date = date else { return "N/A" }
|
// guard let date = date else { return "N/A" }
|
||||||
let formatter = DateFormatter()
|
// let formatter = DateFormatter()
|
||||||
formatter.dateStyle = .short
|
// formatter.dateStyle = .short
|
||||||
formatter.timeStyle = .none
|
// formatter.timeStyle = .none
|
||||||
return formatter.string(from: date)
|
// return formatter.string(from: date)
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct PearcleanerApp: App {
|
struct PearcleanerApp: App {
|
||||||
@@ -14,10 +15,11 @@ struct PearcleanerApp: App {
|
|||||||
@StateObject var appState = AppState()
|
@StateObject var appState = AppState()
|
||||||
@StateObject var locations = Locations()
|
@StateObject var locations = Locations()
|
||||||
@StateObject var fsm = FolderSettingsManager()
|
@StateObject var fsm = FolderSettingsManager()
|
||||||
|
@StateObject private var updater = Updater(owner: "alienator88", repo: "Pearcleaner")
|
||||||
|
@StateObject private var themeManager = ThemeManager.shared
|
||||||
|
@StateObject private var permissionManager = PermissionManager.shared
|
||||||
@State private var windowSettings = WindowSettings()
|
@State private var windowSettings = WindowSettings()
|
||||||
@AppStorage("settings.updater.updateFrequency") private var updateFrequency: UpdateFrequency = .daily
|
|
||||||
@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("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
||||||
@AppStorage("settings.general.features") private var features: String = ""
|
@AppStorage("settings.general.features") private var features: String = ""
|
||||||
@@ -42,8 +44,10 @@ struct PearcleanerApp: App {
|
|||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
.environmentObject(fsm)
|
.environmentObject(fsm)
|
||||||
.environmentObject(ThemeSettings.shared)
|
.environmentObject(themeManager)
|
||||||
.preferredColorScheme(displayMode.colorScheme)
|
.environmentObject(updater)
|
||||||
|
.environmentObject(permissionManager)
|
||||||
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
|
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
|
||||||
.onOpenURL(perform: { url in
|
.onOpenURL(perform: { url in
|
||||||
let deeplinkManager = DeeplinkManager(showPopover: $showPopover)
|
let deeplinkManager = DeeplinkManager(showPopover: $showPopover)
|
||||||
@@ -76,6 +80,11 @@ struct PearcleanerApp: App {
|
|||||||
secondaryButton: .cancel()
|
secondaryButton: .cancel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $updater.showSheet, content: {
|
||||||
|
/// This will show the update sheet based on the frequency check function only
|
||||||
|
updater.getUpdateView()
|
||||||
|
.environmentObject(themeManager)
|
||||||
|
})
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
||||||
if miniView {
|
if miniView {
|
||||||
@@ -98,8 +107,9 @@ struct PearcleanerApp: App {
|
|||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
.environmentObject(fsm)
|
.environmentObject(fsm)
|
||||||
.environmentObject(ThemeSettings.shared)
|
.environmentObject(themeManager)
|
||||||
.preferredColorScheme(displayMode.colorScheme)
|
.environmentObject(permissionManager)
|
||||||
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}, icon: selectedMenubarIcon)
|
}, icon: selectedMenubarIcon)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,30 +117,16 @@ struct PearcleanerApp: App {
|
|||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
Task {
|
Task {
|
||||||
|
|
||||||
|
/// This will check for updates on load based on the update frequency
|
||||||
|
updater.checkAndUpdateIfNeeded()
|
||||||
|
|
||||||
|
/// Get new features
|
||||||
|
getFeatures(appState: appState, features: $features)
|
||||||
|
|
||||||
// Make sure App Support folder exists in the future if needed for storage
|
// Make sure App Support folder exists in the future if needed for storage
|
||||||
//MARK: This is not needed any longer as the update file is stored in /tmp directory. Use in the future for any local db functions the app might need
|
//MARK: This is not needed any longer as the update file is stored in /tmp directory. Use in the future for any local db functions the app might need
|
||||||
// ensureApplicationSupportFolderExists(appState: appState)
|
// ensureApplicationSupportFolderExists(appState: appState)
|
||||||
|
|
||||||
// Check for updates after app launch
|
|
||||||
checkAllPermissions(appState: appState) { results in
|
|
||||||
appState.permissionResults = results
|
|
||||||
if results.allPermissionsGranted {
|
|
||||||
// Get GH releases
|
|
||||||
loadGithubReleases(appState: appState, releaseOnly: true)
|
|
||||||
|
|
||||||
if updateFrequency != .none {
|
|
||||||
// Update checker
|
|
||||||
checkAndUpdateIfNeeded(appState: appState)
|
|
||||||
}
|
|
||||||
// Get new features
|
|
||||||
getFeatures(appState: appState, features: $features)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -140,7 +136,7 @@ struct PearcleanerApp: App {
|
|||||||
.windowStyle(.hiddenTitleBar)
|
.windowStyle(.hiddenTitleBar)
|
||||||
.windowResizability(.contentMinSize)
|
.windowResizability(.contentMinSize)
|
||||||
.commands {
|
.commands {
|
||||||
AppCommands(appState: appState, locations: locations, fsm: fsm)
|
AppCommands(appState: appState, locations: locations, fsm: fsm, updater: updater, themeManager: themeManager)
|
||||||
// CommandGroup(replacing: .newItem, addition: { })
|
// CommandGroup(replacing: .newItem, addition: { })
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -153,9 +149,11 @@ struct PearcleanerApp: App {
|
|||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
.environmentObject(fsm)
|
.environmentObject(fsm)
|
||||||
.environmentObject(ThemeSettings.shared)
|
.environmentObject(themeManager)
|
||||||
|
.environmentObject(updater)
|
||||||
|
.environmentObject(permissionManager)
|
||||||
.toolbarBackground(.clear)
|
.toolbarBackground(.clear)
|
||||||
.preferredColorScheme(displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,8 +162,8 @@ struct PearcleanerApp: App {
|
|||||||
|
|
||||||
|
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
||||||
var observer: NSObjectProtocol?
|
|
||||||
var windowSettings = WindowSettings()
|
var windowSettings = WindowSettings()
|
||||||
|
var themeManager = ThemeManager.shared
|
||||||
|
|
||||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
|
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
|
||||||
@@ -178,72 +176,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|||||||
|
|
||||||
findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings)
|
findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings)
|
||||||
|
|
||||||
self.appearanceCheck()
|
themeManager.setupAppearance()
|
||||||
|
|
||||||
if menubarEnabled {
|
if menubarEnabled {
|
||||||
findAndHideWindows(named: ["Pearcleaner"])
|
findAndHideWindows(named: ["Pearcleaner"])
|
||||||
NSApplication.shared.setActivationPolicy(.accessory)
|
NSApplication.shared.setActivationPolicy(.accessory)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start observing the appearance change
|
|
||||||
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 themesEnabled = UserDefaults.standard.bool(forKey: "settings.interface.themesEnabled")
|
|
||||||
if themeMode == "Auto" && !themesEnabled {
|
|
||||||
self?.appearanceCheck(reset: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func appearanceCheck(reset: Bool = false) {
|
|
||||||
|
|
||||||
let themesEnabled = UserDefaults.standard.bool(forKey: "settings.interface.themesEnabled")
|
|
||||||
let themeMode = UserDefaults.standard.string(forKey: "settings.general.selectedTheme")
|
|
||||||
|
|
||||||
if themesEnabled {
|
|
||||||
// Custom theming is enabled; user's manual settings take priority.
|
|
||||||
} else {
|
|
||||||
switch themeMode {
|
|
||||||
case "Auto":
|
|
||||||
// Auto mode: adjust theme based on system appearance.
|
|
||||||
ThemeSettings.shared.setupInitialColor()
|
|
||||||
updateDisplayModeBasedOnThemeColor()
|
|
||||||
case "Light", "Dark":
|
|
||||||
// Specific mode set: ensure theme color matches the display mode.
|
|
||||||
ThemeSettings.shared.setupInitialColor(forcedDarkMode: themeMode == "Dark")
|
|
||||||
UserDefaults.standard.set(themeMode == "Dark" ? DisplayMode.dark.rawValue : DisplayMode.light.rawValue, forKey: "displayMode")
|
|
||||||
default:
|
|
||||||
// No specific handling needed or unrecognized mode; fall back to default behavior.
|
|
||||||
printOS("No specific theme mode set or unrecognized value.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if reset && themeMode == "Auto" {
|
|
||||||
let dark = isDarkMode()
|
|
||||||
ThemeSettings.shared.resetToDefault(dark: dark)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateDisplayModeBasedOnThemeColor() {
|
|
||||||
let themeColor = ThemeSettings.shared.themeColor
|
|
||||||
if let isLightColor = themeColor.isLight() {
|
|
||||||
let shouldUseDarkMode = !isLightColor
|
|
||||||
UserDefaults.standard.set(shouldUseDarkMode ? DisplayMode.dark.rawValue : DisplayMode.light.rawValue, forKey: "displayMode")
|
|
||||||
} else {
|
|
||||||
// Default to system preference if brightness cannot be determined.
|
|
||||||
let dark = isDarkMode()
|
|
||||||
UserDefaults.standard.set(dark ? DisplayMode.dark.rawValue : DisplayMode.light.rawValue, forKey: "displayMode")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationWillTerminate(_ notification: Notification) {
|
func applicationWillTerminate(_ notification: Notification) {
|
||||||
// Stop observing the appearance change
|
// Perform actions on app termination here
|
||||||
if let observer = observer {
|
|
||||||
DistributedNotificationCenter.default().removeObserver(observer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -255,10 +201,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|||||||
for window in sender.windows {
|
for window in sender.windows {
|
||||||
window.title = "Pearcleaner"
|
window.title = "Pearcleaner"
|
||||||
window.makeKeyAndOrderFront(self)
|
window.makeKeyAndOrderFront(self)
|
||||||
print(windowSettings.loadWindowSettings())
|
|
||||||
updateOnMain(after: 0.1, {
|
updateOnMain(after: 0.1, {
|
||||||
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
||||||
print(window.title)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return true // Indicates you've handled the re-open
|
return true // Indicates you've handled the re-open
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "display-p3",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x00",
|
|
||||||
"green" : "0x00",
|
|
||||||
"red" : "0x00"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xFF",
|
|
||||||
"green" : "0xFF",
|
|
||||||
"red" : "0xFE"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "display-p3",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xFF",
|
|
||||||
"green" : "0xFF",
|
|
||||||
"red" : "0xFF"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearances" : [
|
|
||||||
{
|
|
||||||
"appearance" : "luminosity",
|
|
||||||
"value" : "dark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "display-p3",
|
|
||||||
"components" : {
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x26",
|
|
||||||
"green" : "0x26",
|
|
||||||
"red" : "0x26"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,12 +6,18 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct AboutSettingsTab: View {
|
struct AboutSettingsTab: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
|
@State private var disclose = false
|
||||||
|
@State private var discloseCredits = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
|
let sponsors = Sponsor.sponsors
|
||||||
|
let credits = Credit.credits
|
||||||
|
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +45,7 @@ struct AboutSettingsTab: View {
|
|||||||
Text("GitHub")
|
Text("GitHub")
|
||||||
Text("Submit a bug or feature request via the repo")
|
Text("Submit a bug or feature request via the repo")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -53,133 +59,92 @@ struct AboutSettingsTab: View {
|
|||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
.padding(.top, 5)
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
.padding()
|
|
||||||
|
|
||||||
}
|
HStack{
|
||||||
|
Image(systemName: "dollarsign.circle")
|
||||||
VStack(alignment: .leading) {
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
|
||||||
|
DisclosureGroup(isExpanded: $disclose, content: {
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack(alignment: .leading, spacing: 0) {
|
||||||
|
ForEach(sponsors) { sponsor in
|
||||||
HStack() {
|
HStack() {
|
||||||
Text("Credits").font(.title2)
|
Text(sponsor.name)
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.leading)
|
|
||||||
.padding(.bottom)
|
|
||||||
|
|
||||||
HStack{
|
|
||||||
Image(systemName: "paintbrush.pointed")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
|
|
||||||
VStack(alignment: .leading){
|
|
||||||
Text("Microsoft Designer")
|
|
||||||
Text("Application icon resource")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
|
|
||||||
}
|
|
||||||
Spacer()
|
Spacer()
|
||||||
Button(""){
|
Button(""){
|
||||||
NSWorkspace.shared.open(URL(string: "https://designer.microsoft.com/image-creator")!)
|
NSWorkspace.shared.open(sponsor.url)
|
||||||
}
|
}
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "link", help: "View"))
|
.buttonStyle(SimpleButtonStyle(icon: "link", help: "View", padding: 5))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
.frame(height: 100)
|
||||||
|
.padding(5)
|
||||||
|
}, label: {
|
||||||
|
Text("GitHub Sponsors")
|
||||||
|
.font(.title2)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
HStack{
|
HStack{
|
||||||
Image(systemName: "applescript")
|
Image(systemName: "info.circle")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
|
|
||||||
|
DisclosureGroup(isExpanded: $discloseCredits, content: {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Privacy Guides")
|
|
||||||
Text("Inspired by open-source appcleaner script from Sun Knudsen")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
.lineLimit(2)
|
|
||||||
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button("") {
|
|
||||||
NSWorkspace.shared.open(URL(string: "https://sunknudsen.com/privacy-guides/how-to-clean-uninstall-macos-apps-using-appcleaner-open-source-alternative")!)
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "link", help: "View"))
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
|
|
||||||
|
ForEach(credits) { credit in
|
||||||
HStack{
|
HStack{
|
||||||
Image(systemName: "trash")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
|
|
||||||
VStack(alignment: .leading){
|
VStack(alignment: .leading){
|
||||||
Text("AppCleaner")
|
Text(credit.name)
|
||||||
Text("Inspired by AppCleaner from Freemacsoft")
|
Text(credit.description)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button(""){
|
Button(""){
|
||||||
NSWorkspace.shared.open(URL(string: "https://freemacsoft.net/appcleaner/")!)
|
NSWorkspace.shared.open(credit.url)
|
||||||
}
|
}
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "link", help: "View"))
|
.buttonStyle(SimpleButtonStyle(icon: "link", help: "View"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.leading)
|
}, label: {
|
||||||
|
Text("Credits")
|
||||||
|
.font(.title2)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
HStack{
|
|
||||||
Image(systemName: "dollarsign.circle.fill")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
|
|
||||||
VStack(alignment: .leading){
|
|
||||||
Text("Apple Developer Account Sponsor")
|
|
||||||
Text("Many thanks to user DharsanB")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
Button(""){
|
|
||||||
NSWorkspace.shared.open(URL(string: "https://github.com/dharsanb")!)
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "link", help: "View"))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Text("Made with ❤️ by Alin Lupascu").font(.footnote).padding(.bottom)
|
Text("Made with ❤️ by Alin Lupascu").font(.footnote).padding(.bottom)
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 600)
|
.frame(width: 500)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,3 +172,32 @@ extension Bundle {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: Sponsors
|
||||||
|
struct Sponsor: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
let name: String
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
static let sponsors: [Sponsor] = [
|
||||||
|
Sponsor(name: "DharsanB", url: URL(string: "https://github.com/dharsanb")!),
|
||||||
|
Sponsor(name: "MadMacMad", url: URL(string: "https://github.com/MadMacMad")!),
|
||||||
|
Sponsor(name: "Butterdawgs", url: URL(string: "https://github.com/butterdawgs")!),
|
||||||
|
Sponsor(name: "y-u-s-u-f", url: URL(string: "https://github.com/y-u-s-u-f")!)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: Credits
|
||||||
|
struct Credit: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
// let icon: String
|
||||||
|
let name: String
|
||||||
|
let description: String
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
static let credits: [Credit] = [
|
||||||
|
Credit(name: "Microsoft Designer", description: "Application icon resource", url: URL(string: "https://designer.microsoft.com/image-creator")!),
|
||||||
|
Credit(name: "Privacy Guides", description: "Inspired by open-source appcleaner script from Sun Knudsen", url: URL(string: "https://sunknudsen.com/privacy-guides/how-to-clean-uninstall-macos-apps-using-appcleaner-open-source-alternative")!),
|
||||||
|
Credit(name: "AppCleaner", description: "Inspired by AppCleaner from Freemacsoft", url: URL(string: "https://freemacsoft.net/appcleaner/")!)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct FolderSettingsTab: View {
|
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
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@State private var isHovered = false
|
@State private var isHovered = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -82,8 +83,9 @@ struct FolderSettingsTab: View {
|
|||||||
}
|
}
|
||||||
.scrollIndicators(.automatic)
|
.scrollIndicators(.automatic)
|
||||||
.padding()
|
.padding()
|
||||||
// .background(Color("mode").opacity(0.05))
|
.frame(height: 200)
|
||||||
.background(backgroundView(themeSettings: themeSettings, darker: true))
|
// .background(.primary.opacity(0.05))
|
||||||
|
.background(backgroundView(themeManager: themeManager, 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
|
||||||
@@ -175,8 +177,9 @@ struct FolderSettingsTab: View {
|
|||||||
}
|
}
|
||||||
.scrollIndicators(.automatic)
|
.scrollIndicators(.automatic)
|
||||||
.padding()
|
.padding()
|
||||||
.background(backgroundView(themeSettings: themeSettings, darker: true))
|
.frame(height: 200)
|
||||||
// .background(Color("mode").opacity(0.05))
|
.background(backgroundView(themeManager: themeManager, darker: true))
|
||||||
|
// .background(.primary.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
|
||||||
@@ -200,15 +203,11 @@ struct FolderSettingsTab: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding(.top, 5)
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 600)
|
.frame(width: 500)//, height: 600)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+121
-145
@@ -7,13 +7,13 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
//import ServiceManagement
|
|
||||||
import FinderSync
|
import FinderSync
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
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
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@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
|
||||||
@@ -52,11 +52,11 @@ struct GeneralSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("\(brew ? "Homebrew cleanup is enabled" : "Homebrew cleanup is disabled")")
|
Text("\(brew ? "Homebrew cleanup is enabled" : "Homebrew cleanup is disabled")")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoButton(text: "When homebrew cleanup is enabled, Pearcleaner will check if the app you are removing was installed via homebrew and execute a brew uninstall and brew cleanup command as well to let homebrew know that the app is removed. This way your homebrew list will be synced up correctly and caching will be removed.\n\nNOTE: If you undo the file delete with CMD+Z, the files will be put back but homebrew will not be aware of it. To get the homebrew list back in sync you'd need to run:\n brew install APPNAME --force")
|
InfoButton(text: "When homebrew cleanup is enabled, Pearcleaner will check if the app you are removing was installed via homebrew and execute a brew uninstall and brew cleanup command as well to let homebrew know that the app is removed. This way your homebrew list will be synced up correctly and caching will be removed.\n\nNOTE: If you undo the file delete with CMD+Z, the files will be put back but homebrew will not be aware of it. To get the homebrew list back in sync you'd need to run:\n brew install APPNAME --force")
|
||||||
@@ -76,11 +76,11 @@ struct GeneralSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("\(oneShotMode ? "One-Shot Mode is enabled" : "One-Shot Mode is disabled")")
|
Text("\(oneShotMode ? "One-Shot Mode is enabled" : "One-Shot Mode is disabled")")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoButton(text: "When one-shot mode is enabled, clicking the Uninstall button to remove an app will also close Pearcleaner right after. This only affects Pearcleaner when it is opened via external means, like Sentinel Trash Monitor or the Finder extension. This allows for single use of the app, AKA one-shot mode. When Pearcleaner is opened normally, this setting is ignored and will work as usual.")
|
InfoButton(text: "When one-shot mode is enabled, clicking the Uninstall button to remove an app will also close Pearcleaner right after. This only affects Pearcleaner when it is opened via external means, like Sentinel Trash Monitor or the Finder extension. This allows for single use of the app, AKA one-shot mode. When Pearcleaner is opened normally, this setting is ignored and will work as usual.")
|
||||||
@@ -100,11 +100,11 @@ struct GeneralSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("File list sorting mode")
|
Text("File list sorting mode")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
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()
|
||||||
@@ -114,7 +114,7 @@ struct GeneralSettingsTab: View {
|
|||||||
Text("Size")
|
Text("Size")
|
||||||
.tag(false)
|
.tag(false)
|
||||||
}
|
}
|
||||||
.pickerStyle(themeSettings: themeSettings)
|
.buttonStyle(.borderless)
|
||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
@@ -126,11 +126,11 @@ struct GeneralSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("File size display mode")
|
Text("File size display mode")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.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()
|
||||||
@@ -142,8 +142,7 @@ struct GeneralSettingsTab: View {
|
|||||||
Text("Finder")
|
Text("Finder")
|
||||||
.tag("Finder")
|
.tag("Finder")
|
||||||
}
|
}
|
||||||
.pickerStyle(themeSettings: themeSettings)
|
.buttonStyle(.borderless)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
@@ -153,120 +152,116 @@ struct GeneralSettingsTab: View {
|
|||||||
// === Perms ================================================================================================
|
// === Perms ================================================================================================
|
||||||
|
|
||||||
|
|
||||||
Divider()
|
// Divider()
|
||||||
.padding()
|
// .padding()
|
||||||
|
|
||||||
|
|
||||||
|
// HStack() {
|
||||||
|
// Text("Permissions").font(.title2)
|
||||||
|
//
|
||||||
|
//// InfoButtonPerms()
|
||||||
|
//
|
||||||
|
// Spacer()
|
||||||
|
//
|
||||||
|
// Button("Refresh") {
|
||||||
|
// checkAllPermissions(appState: appState) { results in
|
||||||
|
// updateOnMain {
|
||||||
|
// appState.permissionResults = results
|
||||||
|
// }
|
||||||
|
// diskStatus = results.fullDiskAccess
|
||||||
|
// accessStatus = results.accessibility
|
||||||
|
// autoStatus = results.automation
|
||||||
|
// if results.allPermissionsGranted {
|
||||||
|
// updateOnMain {
|
||||||
|
// appState.permissionsOkay = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", label: "Refresh", help: "Refresh permissions"))
|
||||||
|
// .padding(.trailing, 5)
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// .padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
HStack() {
|
// HStack(spacing: 0) {
|
||||||
Text("Permissions").font(.title2)
|
// Image(systemName: "externaldrive")
|
||||||
|
// .resizable()
|
||||||
InfoButtonPerms()
|
// .scaledToFit()
|
||||||
|
// .frame(width: 20, height: 20)
|
||||||
Spacer()
|
// .padding(.trailing)
|
||||||
|
// .foregroundStyle(diskStatus ? .green : .red)
|
||||||
Button("Refresh") {
|
// .saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
||||||
checkAllPermissions(appState: appState) { results in
|
// Text(diskStatus ? "Full Disk permission granted" : "Full Disk permission not granted")
|
||||||
updateOnMain {
|
// .font(.callout)
|
||||||
appState.permissionResults = results
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
//
|
||||||
diskStatus = results.fullDiskAccess
|
// Spacer()
|
||||||
accessStatus = results.accessibility
|
//
|
||||||
autoStatus = results.automation
|
// Button("") {
|
||||||
if results.allPermissionsGranted {
|
// if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") {
|
||||||
updateOnMain {
|
// NSWorkspace.shared.open(url)
|
||||||
appState.permissionsOkay = true
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View disk permissions pane"))
|
||||||
}
|
//
|
||||||
}
|
// }
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", label: "Refresh", help: "Refresh permissions"))
|
// .padding(5)
|
||||||
.padding(.trailing, 5)
|
// .padding(.leading)
|
||||||
|
//
|
||||||
}
|
//
|
||||||
.padding(.leading)
|
//
|
||||||
|
// HStack(spacing: 0) {
|
||||||
|
// Image(systemName: isVersionOrHigher(version: 14) ? "accessibility" : "figure.arms.open")
|
||||||
HStack(spacing: 0) {
|
// .resizable()
|
||||||
Image(systemName: "externaldrive")
|
// .scaledToFit()
|
||||||
.resizable()
|
// .frame(width: 20, height: 20)
|
||||||
.scaledToFit()
|
// .padding(.trailing)
|
||||||
.frame(width: 20, height: 20)
|
// .foregroundStyle(accessStatus ? .green : .red)
|
||||||
.padding(.trailing)
|
// .saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
||||||
.foregroundStyle(diskStatus ? .green : .red)
|
// Text(accessStatus ? "Accessibility permission granted" : "Accessibility permission not granted")
|
||||||
.saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
// .font(.callout)
|
||||||
Text(diskStatus ? "Full Disk permission granted" : "Full Disk permission not granted")
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
.font(.callout)
|
//
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
// Spacer()
|
||||||
|
//
|
||||||
Spacer()
|
// Button("") {
|
||||||
|
// if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") {
|
||||||
Button("") {
|
// NSWorkspace.shared.open(url)
|
||||||
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") {
|
// }
|
||||||
NSWorkspace.shared.open(url)
|
// }
|
||||||
}
|
// .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View accessibility permissions pane"))
|
||||||
}
|
//
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "folder", help: "View disk permissions pane"))
|
// }
|
||||||
|
// .padding(5)
|
||||||
}
|
// .padding(.leading)
|
||||||
.padding(5)
|
//
|
||||||
.padding(.leading)
|
//
|
||||||
|
// HStack(spacing: 0) {
|
||||||
Spacer()
|
// Image(systemName: "gearshape.2")
|
||||||
|
// .resizable()
|
||||||
|
// .scaledToFit()
|
||||||
HStack(spacing: 0) {
|
// .frame(width: 20, height: 20)
|
||||||
Image(systemName: isMacOS14OrHigher() ? "accessibility" : "figure.arms.open")
|
// .padding(.trailing)
|
||||||
.resizable()
|
// .foregroundStyle(autoStatus ? .green : .red)
|
||||||
.scaledToFit()
|
// .saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
||||||
.frame(width: 20, height: 20)
|
// Text(autoStatus ? "Automation permission granted" : "Automation permission not granted")
|
||||||
.padding(.trailing)
|
// .font(.callout)
|
||||||
.foregroundStyle(accessStatus ? .green : .red)
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
.saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
//
|
||||||
Text(accessStatus ? "Accessibility permission granted" : "Accessibility permission not granted")
|
// Spacer()
|
||||||
.font(.callout)
|
//
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
// Button("") {
|
||||||
|
// if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation") {
|
||||||
Spacer()
|
// NSWorkspace.shared.open(url)
|
||||||
|
// }
|
||||||
Button("") {
|
// }
|
||||||
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") {
|
// .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View automation permissions pane"))
|
||||||
NSWorkspace.shared.open(url)
|
//
|
||||||
}
|
// }
|
||||||
}
|
// .padding(5)
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "folder", help: "View accessibility permissions pane"))
|
// .padding(.leading)
|
||||||
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: "gearshape.2")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(autoStatus ? .green : .red)
|
|
||||||
.saturation(displayMode.colorScheme == .dark ? 0.8 : 1)
|
|
||||||
Text(autoStatus ? "Automation permission granted" : "Automation permission not granted")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button("") {
|
|
||||||
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation") {
|
|
||||||
NSWorkspace.shared.open(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "folder", help: "View automation permissions pane"))
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -291,7 +286,7 @@ struct GeneralSettingsTab: View {
|
|||||||
.saturation(displayMode.colorScheme == .dark ? 0.8 : 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(.primary.opacity(0.5))
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Toggle(isOn: $sentinel, label: {
|
Toggle(isOn: $sentinel, label: {
|
||||||
@@ -332,7 +327,7 @@ struct GeneralSettingsTab: View {
|
|||||||
.saturation(displayMode.colorScheme == .dark ? 0.8 : 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(.primary.opacity(0.5))
|
||||||
InfoButton(text: "Enabling the extension will allow you to right click apps in Finder and quickly uninstall them with Pearcleaner")
|
InfoButton(text: "Enabling the extension will allow you to right click apps in Finder and quickly uninstall them with Pearcleaner")
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -362,33 +357,14 @@ struct GeneralSettingsTab: View {
|
|||||||
}
|
}
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
checkAllPermissions(appState: appState) { results in
|
|
||||||
updateOnMain {
|
|
||||||
appState.permissionResults = results
|
|
||||||
}
|
|
||||||
diskStatus = results.fullDiskAccess
|
|
||||||
accessStatus = results.accessibility
|
|
||||||
autoStatus = results.automation
|
|
||||||
if results.allPermissionsGranted {
|
|
||||||
updateOnMain {
|
|
||||||
appState.permissionsOkay = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appState.updateExtensionStatus()
|
appState.updateExtensionStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 710)
|
.frame(width: 500)//, height: 710)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,24 +9,25 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ServiceManagement
|
import ServiceManagement
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct InterfaceSettingsTab: View {
|
struct InterfaceSettingsTab: 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
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@State private var windowSettings = WindowSettings()
|
@State private var windowSettings = WindowSettings()
|
||||||
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||||
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
@AppStorage("displayMode") var displayMode: DisplayMode = .system
|
// @AppStorage("displayMode") var displayMode: DisplayMode = .system
|
||||||
@AppStorage("settings.general.selectedTab") private var selectedTab: CurrentTabView = .general
|
// @AppStorage("settings.general.selectedTab") private var selectedTab: CurrentTabView = .general
|
||||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||||
@AppStorage("settings.general.dark") var isDark: Bool = true
|
@AppStorage("settings.general.dark") var isDark: Bool = true
|
||||||
@AppStorage("settings.interface.themesEnabled") var themesEnabled: Bool = false
|
// @AppStorage("settings.interface.themesEnabled") var themesEnabled: Bool = false
|
||||||
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
||||||
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
||||||
@AppStorage("settings.general.selectedTheme") var selectedTheme: String = "Auto"
|
// @AppStorage("settings.general.selectedTheme") var selectedTheme: String = "Auto"
|
||||||
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "pear-4"
|
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "pear-4"
|
||||||
@State private var isLaunchAtLoginEnabled: Bool = false
|
@State private var isLaunchAtLoginEnabled: Bool = false
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@@ -51,11 +52,11 @@ struct InterfaceSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("\(glass ? "Transparent material enabled" : "Transparent material disabled")")
|
Text("\(glass ? "Transparent material enabled" : "Transparent material disabled")")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Toggle(isOn: $glass, label: {
|
Toggle(isOn: $glass, label: {
|
||||||
@@ -69,194 +70,216 @@ struct InterfaceSettingsTab: View {
|
|||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: themesEnabled ? "paintpalette.fill" : "paintpalette")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text("\(themesEnabled ? "Custom theming is enabled" : "Custom theming is disabled")")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
Toggle(isOn: $themesEnabled, label: {
|
|
||||||
})
|
|
||||||
.toggleStyle(.switch)
|
|
||||||
.onChange(of: themesEnabled) { newVal in
|
|
||||||
themeSettings.resetToDefault(dark: isDarkMode())
|
|
||||||
if isDarkMode() {
|
|
||||||
displayMode.colorScheme = .dark
|
|
||||||
} else {
|
|
||||||
displayMode.colorScheme = .light
|
|
||||||
}
|
|
||||||
if newVal {
|
|
||||||
selectedTheme = isDarkMode() ? "Dark" : "Light"
|
|
||||||
} else {
|
|
||||||
selectedTheme = "Auto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
|
|
||||||
|
|
||||||
/// Show theme color selector if theming is enabled
|
|
||||||
if themesEnabled {
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Image(systemName: "paintbrush")
|
Image(systemName: "paintbrush")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("Application theme color")
|
Text("Theme Mode")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
ThemeSettingsView(opacity: 1)
|
||||||
|
.onChange(of: themeManager.displayMode) { _ in
|
||||||
HStack(spacing: 10) {
|
|
||||||
Button("") {
|
|
||||||
themeSettings.setPreset(preset: "pearcleaner", colorScheme: displayMode)
|
|
||||||
}
|
|
||||||
.buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "pearcleaner", colorScheme: displayMode), label: "Pearcleaner"))
|
|
||||||
|
|
||||||
Button("") {
|
|
||||||
themeSettings.setPreset(preset: "dracula", colorScheme: displayMode)
|
|
||||||
}
|
|
||||||
.buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "dracula", colorScheme: displayMode), label: "Dracula"))
|
|
||||||
|
|
||||||
Button("") {
|
|
||||||
themeSettings.setPreset(preset: "solarized", colorScheme: displayMode)
|
|
||||||
}
|
|
||||||
.buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "solarized", colorScheme: displayMode), label: "Solarized"))
|
|
||||||
|
|
||||||
Button("") {
|
|
||||||
themeSettings.setPreset(preset: "macOS", colorScheme: displayMode)
|
|
||||||
}
|
|
||||||
.buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "macOS", colorScheme: displayMode), label: "macOS"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
ColorPicker("", selection: $themeSettings.themeColor, supportsOpacity: false)
|
|
||||||
.onChange(of: themeSettings.themeColor) { newValue in
|
|
||||||
themeSettings.saveThemeColor()
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 5)
|
|
||||||
|
|
||||||
Button("") {
|
|
||||||
themeSettings.resetToDefault(dark: isDarkMode())
|
|
||||||
if isDarkMode() {
|
|
||||||
displayMode.colorScheme = .dark
|
|
||||||
} else {
|
|
||||||
displayMode.colorScheme = .light
|
|
||||||
}
|
|
||||||
selectedTheme = isDarkMode() ? "Dark" : "Light"
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Reset color to default"))
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(5)
|
|
||||||
.padding(.leading)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Image(systemName: {
|
|
||||||
switch displayMode {
|
|
||||||
case .dark:
|
|
||||||
return "moon.fill"
|
|
||||||
case .light:
|
|
||||||
return "sun.max.fill"
|
|
||||||
case .system:
|
|
||||||
return "circle.righthalf.filled"
|
|
||||||
}
|
|
||||||
}())
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.padding(.trailing)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
|
||||||
Text(themesEnabled ? "Application font color" : "Application color mode")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
}
|
|
||||||
InfoButton(text: "When custom theming is disabled, you can set the application color mode to follow the operating system using Auto. Or manually set it to Light or Dark. When custom theming is enabled, this selector acts mainly as a font color picker between light or dark to match your custom theme color.")
|
|
||||||
Spacer()
|
|
||||||
Picker("", selection: $selectedTheme) {
|
|
||||||
if !themesEnabled {
|
|
||||||
Text("Auto")
|
|
||||||
.tag("Auto")
|
|
||||||
}
|
|
||||||
Text(themesEnabled ? "Light" : "Dark")
|
|
||||||
.tag("Dark")
|
|
||||||
Text(themesEnabled ? "Dark" : "Light")
|
|
||||||
.tag("Light")
|
|
||||||
}
|
|
||||||
.pickerStyle(themeSettings: themeSettings)
|
|
||||||
.onChange(of: selectedTheme) { newTheme in
|
|
||||||
switch newTheme {
|
|
||||||
case "Auto":
|
|
||||||
if isDarkMode() {
|
|
||||||
displayMode.colorScheme = .dark
|
|
||||||
} else {
|
|
||||||
displayMode.colorScheme = .light
|
|
||||||
}
|
|
||||||
if !themesEnabled {
|
|
||||||
themeSettings.resetToDefault(dark: isDarkMode())
|
|
||||||
}
|
|
||||||
// Refresh foreground colors
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
||||||
self.selectedTab = .interface
|
|
||||||
}
|
|
||||||
if menubarEnabled{
|
|
||||||
MenuBarExtraManager.shared.restartMenuBarExtra()
|
MenuBarExtraManager.shared.restartMenuBarExtra()
|
||||||
}
|
}
|
||||||
case "Dark":
|
|
||||||
displayMode.colorScheme = .dark
|
|
||||||
if !themesEnabled {
|
|
||||||
themeSettings.resetToDefault(dark: true)
|
|
||||||
}
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
||||||
self.selectedTab = .interface
|
|
||||||
}
|
|
||||||
if menubarEnabled{
|
|
||||||
MenuBarExtraManager.shared.restartMenuBarExtra()
|
|
||||||
}
|
|
||||||
case "Light":
|
|
||||||
displayMode.colorScheme = .light
|
|
||||||
if !themesEnabled {
|
|
||||||
themeSettings.resetToDefault(dark: false)
|
|
||||||
}
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
||||||
self.selectedTab = .interface
|
|
||||||
}
|
|
||||||
if menubarEnabled{
|
|
||||||
MenuBarExtraManager.shared.restartMenuBarExtra()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// HStack(spacing: 0) {
|
||||||
|
// Image(systemName: themesEnabled ? "paintpalette.fill" : "paintpalette")
|
||||||
|
// .resizable()
|
||||||
|
// .scaledToFit()
|
||||||
|
// .frame(width: 20, height: 20)
|
||||||
|
// .padding(.trailing)
|
||||||
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
|
// VStack(alignment: .leading, spacing: 5) {
|
||||||
|
// Text("\(themesEnabled ? "Custom theming is enabled" : "Custom theming is disabled")")
|
||||||
|
// .font(.callout)
|
||||||
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
|
// }
|
||||||
|
// Spacer()
|
||||||
|
// Toggle(isOn: $themesEnabled, label: {
|
||||||
|
// })
|
||||||
|
// .toggleStyle(.switch)
|
||||||
|
// .onChange(of: themesEnabled) { newVal in
|
||||||
|
// themeManager.resetToDefault(dark: isDarkMode())
|
||||||
|
// if isDarkMode() {
|
||||||
|
// displayMode.colorScheme = .dark
|
||||||
|
// } else {
|
||||||
|
// displayMode.colorScheme = .light
|
||||||
|
// }
|
||||||
|
// if newVal {
|
||||||
|
// selectedTheme = isDarkMode() ? "Dark" : "Light"
|
||||||
|
// } else {
|
||||||
|
// selectedTheme = "Auto"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .padding(5)
|
||||||
|
// .padding(.leading)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// /// Show theme color selector if theming is enabled
|
||||||
|
// if themesEnabled {
|
||||||
|
// HStack(spacing: 0) {
|
||||||
|
// Image(systemName: "paintbrush")
|
||||||
|
// .resizable()
|
||||||
|
// .scaledToFit()
|
||||||
|
// .frame(width: 20, height: 20)
|
||||||
|
// .padding(.trailing)
|
||||||
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
|
// VStack(alignment: .leading, spacing: 5) {
|
||||||
|
// Text("Application theme color")
|
||||||
|
// .font(.callout)
|
||||||
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Spacer()
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// HStack(spacing: 10) {
|
||||||
|
// Button("") {
|
||||||
|
// themeManager.setPreset(preset: "pearcleaner", colorScheme: displayMode)
|
||||||
|
// }
|
||||||
|
//// .buttonStyle(PresetColor(fillColor: themeManager.getColorForPreset(preset: "pearcleaner", colorScheme: displayMode), label: "Pearcleaner"))
|
||||||
|
//
|
||||||
|
// Button("") {
|
||||||
|
// themeManager.setPreset(preset: "dracula", colorScheme: displayMode)
|
||||||
|
// }
|
||||||
|
//// .buttonStyle(PresetColor(fillColor: themeManager.getColorForPreset(preset: "dracula", colorScheme: displayMode), label: "Dracula"))
|
||||||
|
//
|
||||||
|
// Button("") {
|
||||||
|
// themeManager.setPreset(preset: "solarized", colorScheme: displayMode)
|
||||||
|
// }
|
||||||
|
//// .buttonStyle(PresetColor(fillColor: themeManager.getColorForPreset(preset: "solarized", colorScheme: displayMode), label: "Solarized"))
|
||||||
|
//
|
||||||
|
// Button("") {
|
||||||
|
// themeManager.setPreset(preset: "macOS", colorScheme: displayMode)
|
||||||
|
// }
|
||||||
|
//// .buttonStyle(PresetColor(fillColor: themeManager.getColorForPreset(preset: "macOS", colorScheme: displayMode), label: "macOS"))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Spacer()
|
||||||
|
//
|
||||||
|
// ColorPicker("", selection: $themeManager.themeColor, supportsOpacity: false)
|
||||||
|
// .onChange(of: themeManager.themeColor) { newValue in
|
||||||
|
// themeManager.saveThemeColor()
|
||||||
|
// }
|
||||||
|
// .padding(.horizontal, 5)
|
||||||
|
//
|
||||||
|
// Button("") {
|
||||||
|
// themeManager.resetToDefault(dark: isDarkMode())
|
||||||
|
// if isDarkMode() {
|
||||||
|
// displayMode.colorScheme = .dark
|
||||||
|
// } else {
|
||||||
|
// displayMode.colorScheme = .light
|
||||||
|
// }
|
||||||
|
// selectedTheme = isDarkMode() ? "Dark" : "Light"
|
||||||
|
// }
|
||||||
|
// .buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Reset color to default"))
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// .padding(5)
|
||||||
|
// .padding(.leading)
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// HStack(spacing: 0) {
|
||||||
|
// Image(systemName: {
|
||||||
|
// switch displayMode {
|
||||||
|
// case .dark:
|
||||||
|
// return "moon.fill"
|
||||||
|
// case .light:
|
||||||
|
// return "sun.max.fill"
|
||||||
|
// case .system:
|
||||||
|
// return "circle.righthalf.filled"
|
||||||
|
// }
|
||||||
|
// }())
|
||||||
|
// .resizable()
|
||||||
|
// .scaledToFit()
|
||||||
|
// .frame(width: 20, height: 20)
|
||||||
|
// .padding(.trailing)
|
||||||
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
|
// VStack(alignment: .leading, spacing: 5) {
|
||||||
|
// Text(themesEnabled ? "Application font color" : "Application color mode")
|
||||||
|
// .font(.callout)
|
||||||
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
|
// }
|
||||||
|
// InfoButton(text: "When custom theming is disabled, you can set the application color mode to follow the operating system using Auto. Or manually set it to Light or Dark. When custom theming is enabled, this selector acts mainly as a font color picker between light or dark to match your custom theme color.")
|
||||||
|
// Spacer()
|
||||||
|
// Picker("", selection: $selectedTheme) {
|
||||||
|
// if !themesEnabled {
|
||||||
|
// Text("Auto")
|
||||||
|
// .tag("Auto")
|
||||||
|
// }
|
||||||
|
// Text(themesEnabled ? "Light" : "Dark")
|
||||||
|
// .tag("Dark")
|
||||||
|
// Text(themesEnabled ? "Dark" : "Light")
|
||||||
|
// .tag("Light")
|
||||||
|
// }
|
||||||
|
// .buttonStyle(.borderless)
|
||||||
|
// .onChange(of: selectedTheme) { newTheme in
|
||||||
|
// switch newTheme {
|
||||||
|
// case "Auto":
|
||||||
|
// if isDarkMode() {
|
||||||
|
// displayMode.colorScheme = .dark
|
||||||
|
// } else {
|
||||||
|
// displayMode.colorScheme = .light
|
||||||
|
// }
|
||||||
|
// if !themesEnabled {
|
||||||
|
// themeManager.resetToDefault(dark: isDarkMode())
|
||||||
|
// }
|
||||||
|
// // Refresh foreground colors
|
||||||
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
// self.selectedTab = .interface
|
||||||
|
// }
|
||||||
|
// if menubarEnabled{
|
||||||
|
// MenuBarExtraManager.shared.restartMenuBarExtra()
|
||||||
|
// }
|
||||||
|
// case "Dark":
|
||||||
|
// displayMode.colorScheme = .dark
|
||||||
|
// if !themesEnabled {
|
||||||
|
// themeManager.resetToDefault(dark: true)
|
||||||
|
// }
|
||||||
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
// self.selectedTab = .interface
|
||||||
|
// }
|
||||||
|
// if menubarEnabled{
|
||||||
|
// MenuBarExtraManager.shared.restartMenuBarExtra()
|
||||||
|
// }
|
||||||
|
// case "Light":
|
||||||
|
// displayMode.colorScheme = .light
|
||||||
|
// if !themesEnabled {
|
||||||
|
// themeManager.resetToDefault(dark: false)
|
||||||
|
// }
|
||||||
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
// self.selectedTab = .interface
|
||||||
|
// }
|
||||||
|
// if menubarEnabled{
|
||||||
|
// MenuBarExtraManager.shared.restartMenuBarExtra()
|
||||||
|
// }
|
||||||
|
// default:
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .padding(5)
|
||||||
|
// .padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// === Mini =================================================================================================
|
// === Mini =================================================================================================
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
@@ -271,16 +294,16 @@ struct InterfaceSettingsTab: View {
|
|||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Image(systemName: isMacOS14OrHigher() ? "square.resize.up" : "macwindow.on.rectangle")
|
Image(systemName: isVersionOrHigher(version: 14) ? "square.resize.up" : "macwindow.on.rectangle")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("\(mini ? "Mini window mode" : "Full size window mode")")
|
Text("\(mini ? "Mini window mode" : "Full size window mode")")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Toggle(isOn: $mini, label: {
|
Toggle(isOn: $mini, label: {
|
||||||
@@ -297,8 +320,8 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
.environmentObject(fsm)
|
.environmentObject(fsm)
|
||||||
.environmentObject(ThemeSettings.shared)
|
.environmentObject(themeManager)
|
||||||
.preferredColorScheme(displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}) {
|
}) {
|
||||||
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
||||||
}
|
}
|
||||||
@@ -313,8 +336,8 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
.environmentObject(fsm)
|
.environmentObject(fsm)
|
||||||
.environmentObject(ThemeSettings.shared)
|
.environmentObject(themeManager)
|
||||||
.preferredColorScheme(displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
||||||
@@ -334,11 +357,11 @@ struct InterfaceSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("\(miniView ? "Show apps list on startup" : "Show drop target on startup")")
|
Text("\(miniView ? "Show apps list on startup" : "Show drop target on startup")")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.")
|
InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.")
|
||||||
@@ -361,11 +384,11 @@ struct InterfaceSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("\(popoverStay ? "Popover window will stay on top" : "Popover window will not stay on top")")
|
Text("\(popoverStay ? "Popover window will stay on top" : "Popover window will not stay on top")")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the popover.")
|
InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the popover.")
|
||||||
@@ -397,11 +420,11 @@ struct InterfaceSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("\(menubarEnabled ? "Menubar icon enabled" : "Menubar icon disabled")")
|
Text("\(menubarEnabled ? "Menubar icon enabled" : "Menubar icon disabled")")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
InfoButton(text: "When menubar icon is enabled, the main app window and dock icon will be disabled since the app will be put in accessory mode.")
|
InfoButton(text: "When menubar icon is enabled, the main app window and dock icon will be disabled since the app will be put in accessory mode.")
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -415,8 +438,8 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
.environmentObject(fsm)
|
.environmentObject(fsm)
|
||||||
.environmentObject(ThemeSettings.shared)
|
.environmentObject(themeManager)
|
||||||
.preferredColorScheme(displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}, icon: selectedMenubarIcon)
|
}, icon: selectedMenubarIcon)
|
||||||
NSApplication.shared.setActivationPolicy(.accessory)
|
NSApplication.shared.setActivationPolicy(.accessory)
|
||||||
findAndHideWindows(named: ["Pearcleaner"])
|
findAndHideWindows(named: ["Pearcleaner"])
|
||||||
@@ -431,8 +454,8 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
.environmentObject(fsm)
|
.environmentObject(fsm)
|
||||||
.environmentObject(ThemeSettings.shared)
|
.environmentObject(themeManager)
|
||||||
.preferredColorScheme(displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}) {
|
}) {
|
||||||
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
||||||
}
|
}
|
||||||
@@ -442,8 +465,8 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
.environmentObject(fsm)
|
.environmentObject(fsm)
|
||||||
.environmentObject(ThemeSettings.shared)
|
.environmentObject(themeManager)
|
||||||
.preferredColorScheme(displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}) {
|
}) {
|
||||||
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
||||||
}
|
}
|
||||||
@@ -464,11 +487,11 @@ struct InterfaceSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("\(isLaunchAtLoginEnabled ? "Launch at login enabled" : "Launch at login disabled")")
|
Text("\(isLaunchAtLoginEnabled ? "Launch at login enabled" : "Launch at login disabled")")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
InfoButton(text: "This setting will affect Pearcleaner whether you're running in menubar mode or regular mode. If you disable menubar icon, you might want to disable this as well so Pearcleaner doesn't start on login.")
|
InfoButton(text: "This setting will affect Pearcleaner whether you're running in menubar mode or regular mode. If you disable menubar icon, you might want to disable this as well so Pearcleaner doesn't start on login.")
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -505,11 +528,11 @@ struct InterfaceSettingsTab: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(.trailing)
|
.padding(.trailing)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text("Menubar icon")
|
Text("Menubar icon")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Picker("", selection: $selectedMenubarIcon) {
|
Picker("", selection: $selectedMenubarIcon) {
|
||||||
@@ -533,19 +556,18 @@ struct InterfaceSettingsTab: View {
|
|||||||
.onChange(of: selectedMenubarIcon) { newValue in
|
.onChange(of: selectedMenubarIcon) { newValue in
|
||||||
MenuBarExtraManager.shared.swapMenuBarIcon(icon: newValue)
|
MenuBarExtraManager.shared.swapMenuBarIcon(icon: newValue)
|
||||||
}
|
}
|
||||||
.pickerStyle(themeSettings: themeSettings)
|
.buttonStyle(.borderless)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
Spacer()
|
// Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: themesEnabled ? 600 : 580)
|
.frame(width: 500)//, height: themesEnabled ? 600 : 580)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var fsm: FolderSettingsManager
|
@EnvironmentObject var fsm: FolderSettingsManager
|
||||||
@EnvironmentObject var themeSettings: ThemeSettings
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
|
@EnvironmentObject var updater: Updater
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||||
@@ -42,6 +44,7 @@ struct SettingsView: View {
|
|||||||
Label(CurrentTabView.update.title, systemImage: "cloud")
|
Label(CurrentTabView.update.title, systemImage: "cloud")
|
||||||
}
|
}
|
||||||
.tag(CurrentTabView.update)
|
.tag(CurrentTabView.update)
|
||||||
|
.environmentObject(themeManager)
|
||||||
|
|
||||||
TipsSettingsTab()
|
TipsSettingsTab()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
@@ -55,7 +58,7 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
.tag(CurrentTabView.about)
|
.tag(CurrentTabView.about)
|
||||||
}
|
}
|
||||||
.background(backgroundView(themeSettings: themeSettings, glass: glass))
|
.background(backgroundView(themeManager: themeManager, glass: glass))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct TipsSettingsTab: View {
|
struct TipsSettingsTab: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var themeSettings: ThemeSettings
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@@ -37,16 +38,16 @@ struct TipsSettingsTab: View {
|
|||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
.scrollIndicators(.visible)
|
.scrollIndicators(.visible)
|
||||||
.frame(minHeight: 0, maxHeight: .infinity)
|
.frame(minHeight: 0, maxHeight: 500)
|
||||||
.frame(minWidth: 0, maxWidth: .infinity)
|
.frame(minWidth: 0, maxWidth: .infinity)
|
||||||
.background(backgroundView(themeSettings: themeSettings, darker: true))
|
.background(backgroundView(themeManager: themeManager, darker: true))
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 520)
|
.frame(width: 500)//, height: 520)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,82 +8,34 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct UpdateSettingsTab: View {
|
struct UpdateSettingsTab: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var themeSettings: ThemeSettings
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
|
@EnvironmentObject var updater: Updater
|
||||||
@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.updateFrequency") private var updateFrequency: UpdateFrequency = .daily
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
|
|
||||||
VStack {
|
FrequencyView(updater: updater)
|
||||||
HStack(spacing: 0) {
|
|
||||||
|
|
||||||
Text("Pearcleaner will check for updates")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Picker("", selection: $updateFrequency) {
|
|
||||||
ForEach(UpdateFrequency.allCases, id: \.self) { frequency in
|
|
||||||
Text(frequency.rawValue).tag(frequency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: updateFrequency) { frequency in
|
|
||||||
frequency.updateNextUpdateDate()
|
|
||||||
}
|
|
||||||
.pickerStyle(themeSettings: themeSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
if updateFrequency != .none {
|
|
||||||
HStack {
|
|
||||||
Text("Next update check: \(formattedDate(Date(timeIntervalSinceReferenceDate: nextUpdateDate)))")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundStyle(Color("mode").opacity(0.3))
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
ScrollView {
|
ReleasesView(updater: updater)
|
||||||
VStack() {
|
.frame(maxWidth: .infinity, maxHeight: 400)
|
||||||
ForEach(appState.releases, id: \.id) { release in
|
.backgroundAF(opacity: 0.5)
|
||||||
VStack(alignment: .leading) {
|
|
||||||
LabeledDivider(label: "\(release.tag_name)")
|
|
||||||
Text(release.modifiedBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.frame(minHeight: 0, maxHeight: .infinity)
|
|
||||||
.frame(minWidth: 0, maxWidth: .infinity)
|
|
||||||
// .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))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: 20) {
|
HStack(alignment: .center, spacing: 20) {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button(""){
|
Button(""){
|
||||||
loadGithubReleases(appState: appState)
|
updater.checkForUpdates(showSheet: false)
|
||||||
}
|
}
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Reload release notes"))
|
.buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Refresh updater"))
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
@@ -94,12 +46,12 @@ struct UpdateSettingsTab: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button(""){
|
// Button(""){
|
||||||
loadGithubReleases(appState: appState, manual: true)
|
// updater.checkForUpdates(showSheet: true)
|
||||||
}
|
// }
|
||||||
.buttonStyle(SimpleButtonStyle(icon: "arrow.down.square", help: "Check for updates"))
|
// .buttonStyle(SimpleButtonStyle(icon: "arrow.down.square", help: "Check for updates"))
|
||||||
|
//
|
||||||
Spacer()
|
// Spacer()
|
||||||
|
|
||||||
Button(""){
|
Button(""){
|
||||||
NSWorkspace.shared.open(URL(string: "https://github.com/alienator88/Pearcleaner/releases")!)
|
NSWorkspace.shared.open(URL(string: "https://github.com/alienator88/Pearcleaner/releases")!)
|
||||||
@@ -110,11 +62,9 @@ struct UpdateSettingsTab: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 520)
|
.frame(width: 500)//, height: 520)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct AppListItems: View {
|
struct AppListItems: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var themeSettings: ThemeSettings
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@State private var isHovered = false
|
@State private var isHovered = false
|
||||||
@State private var windowSettings = WindowSettings()
|
@State private var windowSettings = WindowSettings()
|
||||||
@@ -60,21 +61,21 @@ struct AppListItems: View {
|
|||||||
if appInfo.webApp {
|
if appInfo.webApp {
|
||||||
Text("web")
|
Text("web")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(Color("mode").opacity(0.3))
|
.foregroundStyle(.primary.opacity(0.3))
|
||||||
.frame(minWidth: 30, minHeight: 15)
|
.frame(minWidth: 30, minHeight: 15)
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.background(
|
.background(
|
||||||
Capsule().strokeBorder(Color("mode").opacity(0.3), lineWidth: 1)
|
Capsule().strokeBorder(.primary.opacity(0.3), lineWidth: 1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if appInfo.wrapped {
|
if appInfo.wrapped {
|
||||||
Text("iOS")
|
Text("iOS")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(Color("mode").opacity(0.3))
|
.foregroundStyle(.primary.opacity(0.3))
|
||||||
.frame(minWidth: 30, minHeight: 15)
|
.frame(minWidth: 30, minHeight: 15)
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.background(
|
.background(
|
||||||
Capsule().strokeBorder(Color("mode").opacity(0.3), lineWidth: 1)
|
Capsule().strokeBorder(.primary.opacity(0.3), lineWidth: 1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ struct AppListItems: View {
|
|||||||
} 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))
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,14 +113,14 @@ struct AppListItems: View {
|
|||||||
}
|
}
|
||||||
.background{
|
.background{
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(isSelected && !glass ? themeSettings.themeColor : .clear)
|
.fill(isSelected && !glass ? themeManager.pickerColor : .clear)
|
||||||
}
|
}
|
||||||
.overlay{
|
.overlay{
|
||||||
if (isHovered || isSelected) {
|
if (isHovered || isSelected) {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
RoundedRectangle(cornerRadius: 50)
|
RoundedRectangle(cornerRadius: 50)
|
||||||
.fill(isSelected ? Color("pear") : Color("mode").opacity(0.5))
|
.fill(isSelected ? Color("pear") : .primary.opacity(0.5))
|
||||||
.frame(width: isSelected ? 4 : 2, height: 25)
|
.frame(width: isSelected ? 4 : 2, height: 25)
|
||||||
.padding(.trailing, 5)
|
.padding(.trailing, 5)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,15 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct AppSearchView: View {
|
struct AppSearchView: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var themeSettings: ThemeSettings
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@EnvironmentObject var locations: Locations
|
@EnvironmentObject var locations: Locations
|
||||||
@EnvironmentObject var fsm: FolderSettingsManager
|
@EnvironmentObject var fsm: FolderSettingsManager
|
||||||
|
@EnvironmentObject var updater: Updater
|
||||||
|
@EnvironmentObject var permissionManager: PermissionManager
|
||||||
var glass: Bool
|
var glass: Bool
|
||||||
var sidebarWidth: Double
|
var sidebarWidth: Double
|
||||||
var menubarEnabled: Bool
|
var menubarEnabled: Bool
|
||||||
@@ -21,6 +24,7 @@ struct AppSearchView: View {
|
|||||||
@State private var showMenu = false
|
@State private var showMenu = false
|
||||||
@State private var showSys: Bool = true
|
@State private var showSys: Bool = true
|
||||||
@State private var showUsr: Bool = true
|
@State private var showUsr: Bool = true
|
||||||
|
@State private var showFeatures: Bool = false
|
||||||
@Binding var isMenuBar: Bool
|
@Binding var isMenuBar: Bool
|
||||||
@AppStorage("settings.general.selectedSortAppsList") var selectedSortAlpha: Bool = true
|
@AppStorage("settings.general.selectedSortAppsList") var selectedSortAlpha: Bool = true
|
||||||
@State private var progress: Double = 0.0
|
@State private var progress: Double = 0.0
|
||||||
@@ -32,14 +36,25 @@ struct AppSearchView: View {
|
|||||||
.frame(height: 10)
|
.frame(height: 10)
|
||||||
.padding(.top, !isMenuBar ? 25 : 0)
|
.padding(.top, !isMenuBar ? 25 : 0)
|
||||||
|
|
||||||
if appState.updateAvailable {
|
if updater.updateAvailable {
|
||||||
UpdateNotificationView(appState: appState)
|
UpdateButton(updater: updater, dark: false, opacity: 1)
|
||||||
} else if !appState.permissionsOkay {
|
.padding(.horizontal)
|
||||||
PermissionsNotificationView(appState: appState)
|
} else if let _ = permissionManager.results, !permissionManager.allPermissionsGranted {
|
||||||
|
PermissionsView(dark: false, opacity: 1)
|
||||||
|
.padding(.horizontal)
|
||||||
} else if appState.featureAvailable {
|
} else if appState.featureAvailable {
|
||||||
FeatureNotificationView(appState: appState)
|
AlertNotification(label: "New Message!", icon: "star", buttonAction: {
|
||||||
|
showFeatures.toggle()
|
||||||
|
}, btnColor: Color.blue, opacity: 1, themeManager: themeManager)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.sheet(isPresented: $showFeatures, content: {
|
||||||
|
FeatureView()
|
||||||
|
.environmentObject(themeManager)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps)
|
AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps)
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
@@ -137,7 +152,7 @@ struct AppSearchView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80))
|
.background(backgroundView(themeManager: themeManager, glass: glass).padding(-80))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,6 +163,7 @@ struct AppSearchView: View {
|
|||||||
.padding(.horizontal, search.isEmpty ? 10 : 5)
|
.padding(.horizontal, search.isEmpty ? 10 : 5)
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var filteredApps: [AppInfo] {
|
private var filteredApps: [AppInfo] {
|
||||||
@@ -181,7 +197,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
|||||||
@State var padding: CGFloat = 5
|
@State var padding: CGFloat = 5
|
||||||
@State var sidebar: Bool = true
|
@State var sidebar: Bool = true
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var themeSettings: ThemeSettings
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
||||||
|
|
||||||
@@ -189,7 +205,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
|||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.fill(darker ? themeSettings.themeColor.darker(by: 5) : themeSettings.themeColor)
|
.fill(darker ? themeManager.pickerColor.adjustBrightness(5) : themeManager.pickerColor)
|
||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.opacity((glass && (sidebar || !mini && !menubarEnabled)) || mini || menubarEnabled ? 0.0 : 1.0)
|
.opacity((glass && (sidebar || !mini && !menubarEnabled)) || mini || menubarEnabled ? 0.0 : 1.0)
|
||||||
@@ -201,7 +217,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(Color("mode").opacity(0.2))
|
.foregroundColor(.primary.opacity(0.2))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,7 +259,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
|||||||
extension NSTextView {
|
extension NSTextView {
|
||||||
open override var frame: CGRect {
|
open override var frame: CGRect {
|
||||||
didSet {
|
didSet {
|
||||||
insertionPointColor = NSColor(Color("mode").opacity(0.2))//.clear
|
insertionPointColor = NSColor(.primary.opacity(0.2))//.clear
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,13 +73,13 @@ struct Header: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("\(title)").foregroundStyle(Color("mode")).opacity(0.5)
|
Text("\(title)").foregroundStyle(.primary).opacity(0.5)
|
||||||
|
|
||||||
Text("\(count)")
|
Text("\(count)")
|
||||||
.font(.system(size: 10))
|
.font(.system(size: 10))
|
||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
.frame(minWidth: count > 99 ? 30 : 24, minHeight: 17)
|
.frame(minWidth: count > 99 ? 30 : 24, minHeight: 17)
|
||||||
.background(Color("mode").opacity(0.1))
|
.background(.primary.opacity(0.1))
|
||||||
.clipShape(.capsule)
|
.clipShape(.capsule)
|
||||||
.padding(.leading, 2)
|
.padding(.leading, 2)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct ConditionBuilderView: View {
|
struct ConditionBuilderView: View {
|
||||||
@Binding var showAlert:Bool
|
@Binding var showAlert:Bool
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct FilesView: View {
|
struct FilesView: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@@ -51,7 +52,7 @@ struct FilesView: View {
|
|||||||
VStack {
|
VStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Searching the file system").font(.title3)
|
Text("Searching the file system").font(.title3)
|
||||||
.foregroundStyle((Color("mode").opacity(0.5)))
|
.foregroundStyle((.primary.opacity(0.5)))
|
||||||
|
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(.linear)
|
.progressViewStyle(.linear)
|
||||||
@@ -59,7 +60,7 @@ struct FilesView: View {
|
|||||||
|
|
||||||
Text("\(elapsedTime)")
|
Text("\(elapsedTime)")
|
||||||
.font(.title).monospacedDigit()
|
.font(.title).monospacedDigit()
|
||||||
.foregroundStyle((Color("mode").opacity(0.5)))
|
.foregroundStyle((.primary.opacity(0.5)))
|
||||||
.opacity(elapsedTime == 0 ? 0 : 1)
|
.opacity(elapsedTime == 0 ? 0 : 1)
|
||||||
.contentTransition(.numericText())
|
.contentTransition(.numericText())
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ struct FilesView: View {
|
|||||||
Text("\(appState.appInfo.bundleIdentifier)")
|
Text("\(appState.appInfo.bundleIdentifier)")
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundStyle((Color("mode").opacity(0.5)))
|
.foregroundStyle((.primary.opacity(0.5)))
|
||||||
}
|
}
|
||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
@@ -143,7 +144,7 @@ struct FilesView: View {
|
|||||||
|
|
||||||
Text("\(displaySizeTotal)").font(.title).fontWeight(.bold).help("Total size on disk")
|
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")")
|
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((Color("mode").opacity(0.5)))
|
.font(.callout).foregroundStyle((.primary.opacity(0.5)))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -177,11 +178,11 @@ struct FilesView: View {
|
|||||||
if appState.appInfo.webApp {
|
if appState.appInfo.webApp {
|
||||||
Text("web")
|
Text("web")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.frame(minWidth: 30, minHeight: 15)
|
.frame(minWidth: 30, minHeight: 15)
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.padding(.horizontal, 2)
|
.padding(.horizontal, 2)
|
||||||
.background(Color("mode").opacity(0.1))
|
.background(.primary.opacity(0.1))
|
||||||
.clipShape(.capsule)
|
.clipShape(.capsule)
|
||||||
.help("This is a web app")
|
.help("This is a web app")
|
||||||
|
|
||||||
@@ -190,11 +191,11 @@ struct FilesView: View {
|
|||||||
if appState.appInfo.wrapped {
|
if appState.appInfo.wrapped {
|
||||||
Text("iOS")
|
Text("iOS")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.frame(minWidth: 30, minHeight: 15)
|
.frame(minWidth: 30, minHeight: 15)
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.padding(.horizontal, 2)
|
.padding(.horizontal, 2)
|
||||||
.background(Color("mode").opacity(0.1))
|
.background(.primary.opacity(0.1))
|
||||||
.clipShape(.capsule)
|
.clipShape(.capsule)
|
||||||
.help("This is a wrapped iOS app")
|
.help("This is a wrapped iOS app")
|
||||||
|
|
||||||
@@ -203,11 +204,11 @@ struct FilesView: View {
|
|||||||
if appState.appInfo.arch != .empty {
|
if appState.appInfo.arch != .empty {
|
||||||
Text("\(appState.appInfo.arch)")
|
Text("\(appState.appInfo.arch)")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.frame(minWidth: 30, minHeight: 15)
|
.frame(minWidth: 30, minHeight: 15)
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.padding(.horizontal, 2)
|
.padding(.horizontal, 2)
|
||||||
.background(Color("mode").opacity(0.1))
|
.background(.primary.opacity(0.1))
|
||||||
.clipShape(.capsule)
|
.clipShape(.capsule)
|
||||||
.help("This bundle's architecture is \(appState.appInfo.arch)")
|
.help("This bundle's architecture is \(appState.appInfo.arch)")
|
||||||
|
|
||||||
@@ -215,11 +216,11 @@ struct FilesView: View {
|
|||||||
|
|
||||||
Text(appState.appInfo.system ? "system" : "user")
|
Text(appState.appInfo.system ? "system" : "user")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.frame(minWidth: 30, minHeight: 15)
|
.frame(minWidth: 30, minHeight: 15)
|
||||||
.padding(2)
|
.padding(2)
|
||||||
.padding(.horizontal, 2)
|
.padding(.horizontal, 2)
|
||||||
.background(Color("mode").opacity(0.1))
|
.background(.primary.opacity(0.1))
|
||||||
.clipShape(.capsule)
|
.clipShape(.capsule)
|
||||||
.help("This app is located in \(appState.appInfo.system ? "/Applications" : "\(home)")")
|
.help("This app is located in \(appState.appInfo.system ? "/Applications" : "\(home)")")
|
||||||
}
|
}
|
||||||
@@ -293,7 +294,7 @@ 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: Color("mode").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)
|
||||||
|
|
||||||
|
|
||||||
@@ -470,7 +471,7 @@ struct FileDetailsItem: View {
|
|||||||
VStack {
|
VStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.fill(Color("mode").opacity(0.5))
|
.fill(.primary.opacity(0.5))
|
||||||
.frame(height: 1.5)
|
.frame(height: 1.5)
|
||||||
.offset(y: 3)
|
.offset(y: 3)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct MiniMode: View {
|
struct MiniMode: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var themeSettings: ThemeSettings
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@State private var showSys: Bool = true
|
@State private var showSys: Bool = true
|
||||||
@State private var showUsr: Bool = true
|
@State private var showUsr: Bool = true
|
||||||
@@ -38,7 +39,7 @@ struct MiniMode: View {
|
|||||||
}
|
}
|
||||||
.frame(minWidth: 300, minHeight: 345)
|
.frame(minWidth: 300, minHeight: 345)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.background(backgroundView(themeSettings: themeSettings, glass: glass))
|
.background(backgroundView(themeManager: themeManager, glass: glass))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +152,7 @@ struct MiniAppView: View {
|
|||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var locations: Locations
|
@EnvironmentObject var locations: Locations
|
||||||
@EnvironmentObject var themeSettings: ThemeSettings
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
// @State private var animateGradient: Bool = false
|
// @State private var animateGradient: Bool = false
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@State private var showSys: Bool = true
|
@State private var showSys: Bool = true
|
||||||
@@ -176,7 +177,7 @@ struct MiniAppView: View {
|
|||||||
ProgressView() {
|
ProgressView() {
|
||||||
Text("Gathering app details")
|
Text("Gathering app details")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.padding(5)
|
.padding(5)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -190,7 +191,7 @@ struct MiniAppView: View {
|
|||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
.frame(minWidth: 300, minHeight: 370)
|
.frame(minWidth: 300, minHeight: 370)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80))
|
.background(backgroundView(themeManager: themeManager, glass: glass).padding(-80))
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
.popover(isPresented: $showPopover, arrowEdge: .trailing) {
|
.popover(isPresented: $showPopover, arrowEdge: .trailing) {
|
||||||
VStack {
|
VStack {
|
||||||
@@ -204,7 +205,7 @@ struct MiniAppView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.interactiveDismissDisabled(popoverStay)
|
.interactiveDismissDisabled(popoverStay)
|
||||||
.background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80))
|
.background(backgroundView(themeManager: themeManager, glass: glass).padding(-80))
|
||||||
.frame(width: 650, height: 500)
|
.frame(width: 650, height: 500)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ extension View {
|
|||||||
// content: { item, isSelected in
|
// content: { item, isSelected in
|
||||||
// Text(item)
|
// Text(item)
|
||||||
// .font(.callout)
|
// .font(.callout)
|
||||||
// .foregroundColor(isSelected ? Color("mode") : Color("mode").opacity(0.5))
|
// .foregroundColor(isSelected ? .primary : .primary.opacity(0.5))
|
||||||
// .padding(.horizontal)
|
// .padding(.horizontal)
|
||||||
// .padding(.bottom, 5)
|
// .padding(.bottom, 5)
|
||||||
// .frame(width: 75)
|
// .frame(width: 75)
|
||||||
@@ -161,7 +161,7 @@ extension View {
|
|||||||
// content: { item, isSelected in
|
// content: { item, isSelected in
|
||||||
// Text(item)
|
// Text(item)
|
||||||
// .font(.callout)
|
// .font(.callout)
|
||||||
// .foregroundColor(isSelected ? Color("mode") : Color("mode").opacity(0.5) )
|
// .foregroundColor(isSelected ? .primary : .primary.opacity(0.5) )
|
||||||
// .padding(.horizontal)
|
// .padding(.horizontal)
|
||||||
// .padding(.bottom, 5)
|
// .padding(.bottom, 5)
|
||||||
// .frame(width: 75)
|
// .frame(width: 75)
|
||||||
@@ -198,7 +198,7 @@ extension View {
|
|||||||
// content: { item, isSelected in
|
// content: { item, isSelected in
|
||||||
// Text(item)
|
// Text(item)
|
||||||
// .font(.callout)
|
// .font(.callout)
|
||||||
// .foregroundColor(isSelected ? Color("mode") : Color("mode").opacity(0.5) )
|
// .foregroundColor(isSelected ? .primary : .primary.opacity(0.5) )
|
||||||
// .padding(.horizontal)
|
// .padding(.horizontal)
|
||||||
// .padding(.bottom, 5)
|
// .padding(.bottom, 5)
|
||||||
// .frame(width: 65)
|
// .frame(width: 65)
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct RegularMode: View {
|
struct RegularMode: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var themeSettings: ThemeSettings
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@EnvironmentObject var locations: Locations
|
@EnvironmentObject var locations: Locations
|
||||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||||
@AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280
|
@AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280
|
||||||
@@ -36,7 +37,7 @@ struct RegularMode: View {
|
|||||||
ProgressView() {
|
ProgressView() {
|
||||||
Text("Gathering app details")
|
Text("Gathering app details")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.padding(5)
|
.padding(5)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -49,11 +50,11 @@ struct RegularMode: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.background(backgroundView(themeSettings: themeSettings, darker: true, glass: glass))
|
.background(backgroundView(themeManager: themeManager, darker: true, glass: glass))
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
|
|
||||||
SlideableDivider(dimension: $sidebarWidth)
|
SlideableDivider(dimension: $sidebarWidth)
|
||||||
.background(backgroundView(themeSettings: themeSettings))
|
.background(backgroundView(themeManager: themeManager))
|
||||||
.zIndex(3)
|
.zIndex(3)
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ struct RegularMode: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.zIndex(2)
|
.zIndex(2)
|
||||||
.background(backgroundView(themeSettings: themeSettings))
|
.background(backgroundView(themeManager: themeManager))
|
||||||
}
|
}
|
||||||
.frame(minWidth: 900, minHeight: 600)
|
.frame(minWidth: 900, minHeight: 600)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct ZombieView: View {
|
struct ZombieView: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@@ -45,7 +46,7 @@ struct ZombieView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("Searching the file system").font(.title3)
|
Text("Searching the file system").font(.title3)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(.linear)
|
.progressViewStyle(.linear)
|
||||||
@@ -54,7 +55,7 @@ struct ZombieView: View {
|
|||||||
|
|
||||||
Text("\(elapsedTime)")
|
Text("\(elapsedTime)")
|
||||||
.font(.title).monospacedDigit()
|
.font(.title).monospacedDigit()
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
.opacity(elapsedTime == 0 ? 0 : 1)
|
.opacity(elapsedTime == 0 ? 0 : 1)
|
||||||
.contentTransition(.numericText())
|
.contentTransition(.numericText())
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ struct ZombieView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
Text("Remaining files and folders from previous applications")
|
Text("Remaining files and folders from previous applications")
|
||||||
.font(.callout).foregroundStyle(Color("mode").opacity(0.5))
|
.font(.callout).foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -125,7 +126,7 @@ struct ZombieView: View {
|
|||||||
Text("\(displaySizeTotal)").font(.title).fontWeight(.bold).help("Total size on disk")
|
Text("\(displaySizeTotal)").font(.title).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) / \(searchZ.isEmpty ? appState.zombieFile.fileSize.count : memoizedFiles.count) \(appState.zombieFile.fileSize.count == 1 ? "item" : "items")")
|
||||||
.font(.callout).foregroundStyle(Color("mode").opacity(0.5))
|
.font(.callout).foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -258,17 +259,28 @@ struct ZombieView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
updateOnMain {
|
|
||||||
// Remove items from the list
|
// Remove items from the list
|
||||||
appState.zombieFile.fileSize = appState.zombieFile.fileSize.filter { !selectedZombieItemsLocal.contains($0.key) }
|
appState.zombieFile.fileSize = appState.zombieFile.fileSize.filter { !selectedZombieItemsLocal.contains($0.key) }
|
||||||
// Update the selectedZombieFiles to remove references that are no longer present
|
appState.zombieFile.fileSizeLogical = appState.zombieFile.fileSizeLogical.filter { !selectedZombieItemsLocal.contains($0.key) }
|
||||||
selectedZombieItemsLocal.removeAll()
|
appState.zombieFile.fileIcon = appState.zombieFile.fileIcon.filter { !selectedZombieItemsLocal.contains($0.key) }
|
||||||
updateTotalSizes()
|
|
||||||
|
|
||||||
|
// Clear the selection
|
||||||
|
selectedZombieItemsLocal.removeAll()
|
||||||
|
|
||||||
|
// Update memoized files and total sizes
|
||||||
|
updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateOnMain {
|
||||||
|
// // Remove items from the list
|
||||||
|
// appState.zombieFile.fileSize = appState.zombieFile.fileSize.filter { !selectedZombieItemsLocal.contains($0.key) }
|
||||||
|
// // Update the selectedZombieFiles to remove references that are no longer present
|
||||||
|
// selectedZombieItemsLocal.removeAll()
|
||||||
|
// updateTotalSizes()
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true)
|
// updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true)
|
||||||
@@ -472,7 +484,7 @@ struct ZombieFileDetailsItem: View {
|
|||||||
VStack {
|
VStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.fill(Color("mode").opacity(0.5))
|
.fill(.primary.opacity(0.5))
|
||||||
.frame(height: 1.5)
|
.frame(height: 1.5)
|
||||||
.offset(y: 3)
|
.offset(y: 3)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
struct FeatureView: View {
|
struct FeatureView: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
@AppStorage("settings.general.features") private var features: String = ""
|
@AppStorage("settings.general.features") private var features: String = ""
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -20,34 +21,39 @@ struct FeatureView: View {
|
|||||||
|
|
||||||
Text("New features for v\((Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)!)!") .font(.title)
|
Text("New features for v\((Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)!)!") .font(.title)
|
||||||
.bold()
|
.bold()
|
||||||
.padding(.vertical)
|
// .padding(.bottom)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
.padding([.horizontal])
|
.padding([.vertical])
|
||||||
|
|
||||||
Text(features)
|
Text(features)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.padding()
|
.padding(.horizontal)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: 20) {
|
HStack(alignment: .center, spacing: 20) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
NewWin.close()
|
dismiss()
|
||||||
|
// NewWin.close()
|
||||||
}) {
|
}) {
|
||||||
Text("Okay")
|
Text("Close")
|
||||||
}
|
}
|
||||||
.buttonStyle(SimpleButtonBrightStyle(icon: "checkmark.circle", label: "Ok", help: "Ok", color: Color("mode")))
|
// .buttonStyle(SimpleButtonBrightStyle(icon: "checkmark.circle", label: "Ok", help: "Ok", color: .primary))
|
||||||
}
|
}
|
||||||
.padding(.bottom)
|
.padding(.vertical)
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0))
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.background(themeManager.pickerColor)
|
||||||
|
|
||||||
|
// .padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,15 +67,6 @@ class NewWin: NSWindowController {
|
|||||||
private func makeNewView(appState: AppState, newWin: NewWindow) -> some View {
|
private func makeNewView(appState: AppState, newWin: NewWindow) -> some View {
|
||||||
|
|
||||||
switch newWin {
|
switch newWin {
|
||||||
case .update:
|
|
||||||
return AnyView(UpdateView()
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.background(.black.opacity(0.2))
|
|
||||||
.environmentObject(appState))
|
|
||||||
case .no_update:
|
|
||||||
return AnyView(NoUpdateView()
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.environmentObject(appState))
|
|
||||||
case .perm:
|
case .perm:
|
||||||
return AnyView(PermView()
|
return AnyView(PermView()
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ struct PermView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
Text("Full Disk Access permission to find and delete files in system paths")
|
Text("Full Disk Access permission to find and delete files in system paths")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(alignment: .top, spacing: 20) {
|
HStack(alignment: .top, spacing: 20) {
|
||||||
@@ -50,10 +50,10 @@ struct PermView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
Text("Accessibility permission to delete files via Finder")
|
Text("Accessibility permission to delete files via Finder")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(alignment: .top, spacing: 20) {
|
HStack(alignment: .top, spacing: 20) {
|
||||||
@@ -61,10 +61,10 @@ struct PermView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
Text("Automation permission to perform extension actions via Finder")
|
Text("Automation permission to perform extension actions via Finder")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(alignment: .top, spacing: 20) {
|
HStack(alignment: .top, spacing: 20) {
|
||||||
@@ -72,10 +72,10 @@ struct PermView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
Text("Reminders permission to find and delete some reminder files that certain apps cache")
|
Text("Reminders permission to find and delete some reminder files that certain apps cache")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(alignment: .top, spacing: 20) {
|
HStack(alignment: .top, spacing: 20) {
|
||||||
@@ -83,10 +83,10 @@ struct PermView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
Text("Add Pearcleaner in both Privacy panes via the + or by dragging the app over the pane. If the app is already pre-populated in the list, just toggle On if neeeded. Restart app when both permissions are granted")
|
Text("Add Pearcleaner in both Privacy panes via the + or by dragging the app over the pane. If the app is already pre-populated in the list, just toggle On if neeeded. Restart app when both permissions are granted")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
@@ -97,7 +97,7 @@ struct PermView: View {
|
|||||||
|
|
||||||
if #available(macOS 14.0, *) {
|
if #available(macOS 14.0, *) {
|
||||||
SettingsLink {}
|
SettingsLink {}
|
||||||
.buttonStyle(SimpleButtonBrightStyle(icon: "gear", label: "Settings", help: "Check permissions in Settings", color: Color("mode")))
|
.buttonStyle(SimpleButtonBrightStyle(icon: "gear", label: "Settings", help: "Check permissions in Settings", color: .primary))
|
||||||
} else {
|
} else {
|
||||||
Button("Settings") {
|
Button("Settings") {
|
||||||
if #available(macOS 13.0, *) {
|
if #available(macOS 13.0, *) {
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
//
|
|
||||||
// UpdateView.swift
|
|
||||||
// Pearcleaner
|
|
||||||
//
|
|
||||||
// Created by Alin Lupascu on 11/16/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct UpdateView: View {
|
|
||||||
@EnvironmentObject var appState: AppState
|
|
||||||
@State private var isAppInCorrectDirectory: Bool = false
|
|
||||||
@State private var isUserAdmin: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 5) {
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
Text("App: \(Bundle.main.version)")
|
|
||||||
.font(.title3)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.opacity(0.5)
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Text("\(appState.progressBar.1 != 1.0 ? "Update Available 🥳" : "Completed 🚀")")
|
|
||||||
.font(.title)
|
|
||||||
.bold()
|
|
||||||
.padding(.vertical)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Text("GitHub: \(appState.releases.first?.tag_name ?? "")")
|
|
||||||
.font(.title3)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.opacity(0.5)
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
.padding([.horizontal])
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
Text("\(appState.releases.first?.modifiedBody ?? "No release information")")
|
|
||||||
.font(.body)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
.padding(20)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
if !isAppInCorrectDirectory {
|
|
||||||
Text("To avoid permission issues, please move Pearcleaner to the \(isUserAdmin ? "/Applications" : "\(home)/Applications") folder before updating!")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(Color("mode"))
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.top, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack() {
|
|
||||||
ProgressView("\(appState.progressBar.0)", value: appState.progressBar.1, total: 1.0)
|
|
||||||
.progressViewStyle(LinearProgressViewStyle(tint: .blue))
|
|
||||||
.frame(height: 10)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: 20) {
|
|
||||||
Button(action: NewWin.close) {
|
|
||||||
Text("Cancel")
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonBrightStyle(icon: "x.circle", label: "Close", help: "Close", color: .red))
|
|
||||||
|
|
||||||
if appState.progressBar.1 != 1.0 {
|
|
||||||
Button(action: {
|
|
||||||
downloadUpdate(appState: appState)
|
|
||||||
}) {
|
|
||||||
Text("Update")
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonBrightStyle(icon: "arrow.down.circle", label: "Update", help: "Update", color: Color("mode")))
|
|
||||||
} else {
|
|
||||||
Button(action: {
|
|
||||||
NewWin.close()
|
|
||||||
relaunchApp(afterDelay: 1)
|
|
||||||
}) {
|
|
||||||
Text("Restart")
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonBrightStyle(icon: "arrow.uturn.left.circle", label: "Restart", help: "Restart", color: Color("mode")))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(.bottom)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0))
|
|
||||||
.onAppear {
|
|
||||||
checkAppDirectoryAndUserRole { result in
|
|
||||||
isAppInCorrectDirectory = result.isInCorrectDirectory
|
|
||||||
isUserAdmin = result.isAdmin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct NoUpdateView: View {
|
|
||||||
@EnvironmentObject var appState: AppState
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 5) {
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
Text("App: \(Bundle.main.version)")
|
|
||||||
.font(.title3)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.opacity(0.5)
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Text("\(appState.progressBar.1 != 1.0 ? "No Update 😌" : "Completed 🚀")")
|
|
||||||
.font(.title)
|
|
||||||
.bold()
|
|
||||||
.padding(.vertical)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Text("GitHub: \(appState.releases.first?.tag_name ?? "")")
|
|
||||||
.font(.title3)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.opacity(0.5)
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
.padding([.horizontal])
|
|
||||||
|
|
||||||
Text("Pearcleaner is on the most current release available, but you may force a re-download of the same version below.")
|
|
||||||
.font(.body)
|
|
||||||
.lineLimit(3)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
VStack() {
|
|
||||||
ProgressView("\(appState.progressBar.0)", value: appState.progressBar.1, total: 1.0)
|
|
||||||
.progressViewStyle(LinearProgressViewStyle(tint: .blue))
|
|
||||||
.frame(height: 10)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: 20) {
|
|
||||||
if appState.progressBar.1 < 0.1 {
|
|
||||||
Button(action: {
|
|
||||||
downloadUpdate(appState: appState)
|
|
||||||
}) {
|
|
||||||
Text("Force Update")
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonBrightStyle(icon: "arrow.down.circle", label: "Force Update", help: "Force-Update", color: .red))
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
NewWin.close()
|
|
||||||
}) {
|
|
||||||
Text("Okay")
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonBrightStyle(icon: "checkmark.circle", label: "Ok", help: "Ok", color: Color("mode")))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if appState.progressBar.1 == 1.0 {
|
|
||||||
Button(action: {
|
|
||||||
NewWin.close()
|
|
||||||
relaunchApp()
|
|
||||||
}) {
|
|
||||||
Text("Restart")
|
|
||||||
}
|
|
||||||
.buttonStyle(SimpleButtonBrightStyle(icon: "arrow.uturn.left.circle", label: "Restart", help: "Restart", color: Color("mode")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.bottom)
|
|
||||||
|
|
||||||
}
|
|
||||||
.padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AlinFoundation
|
||||||
|
|
||||||
class WindowSettings {
|
class WindowSettings {
|
||||||
private let windowWidthKey = "windowWidthKey"
|
private let windowWidthKey = "windowWidthKey"
|
||||||
|
|||||||
Reference in New Issue
Block a user