diff --git a/Assets/Demo.gif b/Assets/Demo.gif new file mode 100644 index 0000000..92c3ed7 Binary files /dev/null and b/Assets/Demo.gif differ diff --git a/Assets/Logo.png b/Assets/Logo.png new file mode 100644 index 0000000..e9b9456 Binary files /dev/null and b/Assets/Logo.png differ diff --git a/README.md b/README.md index c1eb693..342ac5c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ -# XcodeCleaner -MacOS application that allows you to analyze and free up disk space used by XCODE. +

+
+ + + +

+ +## Installation +To install Xcode Cleaner, go to [releases](https://github.com/IrelDev/XcodeCleaner/releases) page and download the `Xcode Cleaner.app` you need. If you prefer to build the project yourself just clone the `master` branch. + +## Demo +

+
+

diff --git a/XcodeCleaner.xcodeproj/project.pbxproj b/XcodeCleaner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..788698e --- /dev/null +++ b/XcodeCleaner.xcodeproj/project.pbxproj @@ -0,0 +1,603 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 9DCE1C0B24B582D200E49E6D /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCE1C0A24B582D200E49E6D /* CoreDataManager.swift */; }; + 9DD3BA7024B4639400EDA308 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA6F24B4639400EDA308 /* AppDelegate.swift */; }; + 9DD3BA7224B4639400EDA308 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7124B4639400EDA308 /* ContentView.swift */; }; + 9DD3BA7524B4639400EDA308 /* XcodeCleaner.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7324B4639400EDA308 /* XcodeCleaner.xcdatamodeld */; }; + 9DD3BA7724B4639700EDA308 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7624B4639700EDA308 /* Assets.xcassets */; }; + 9DD3BA7A24B4639700EDA308 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7924B4639700EDA308 /* Preview Assets.xcassets */; }; + 9DD3BA7D24B4639700EDA308 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7B24B4639700EDA308 /* Main.storyboard */; }; + 9DD3BA8B24B465B800EDA308 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA8924B465B800EDA308 /* README.md */; }; + 9DD3BA8C24B465B800EDA308 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA8A24B465B800EDA308 /* LICENSE */; }; + 9DD3BA8E24B478AE00EDA308 /* DirectoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA8D24B478AE00EDA308 /* DirectoryManager.swift */; }; + 9DD3BA9024B478B600EDA308 /* DirectoryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA8F24B478B600EDA308 /* DirectoryType.swift */; }; + 9DD3BA9424B478C900EDA308 /* DirectoryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9224B478C900EDA308 /* DirectoryModel.swift */; }; + 9DD3BA9524B478C900EDA308 /* StatisticModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9324B478C900EDA308 /* StatisticModel.swift */; }; + 9DD3BA9E24B4791900EDA308 /* DropDownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9B24B4791900EDA308 /* DropDownView.swift */; }; + 9DD3BA9F24B4791900EDA308 /* BodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9C24B4791900EDA308 /* BodyView.swift */; }; + 9DD3BAA024B4791900EDA308 /* DirectoryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9D24B4791900EDA308 /* DirectoryListView.swift */; }; + 9DD3BAA224B4793900EDA308 /* FooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAA124B4793900EDA308 /* FooterView.swift */; }; + 9DD3BAA424B4794300EDA308 /* DividerButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAA324B4794300EDA308 /* DividerButtonView.swift */; }; + 9DD3BAA624B4794800EDA308 /* StatisticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAA524B4794800EDA308 /* StatisticView.swift */; }; + 9DD3BAA824B4794C00EDA308 /* ScanProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAA724B4794C00EDA308 /* ScanProgressView.swift */; }; + 9DD3BAAE24B4796C00EDA308 /* StatisticViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAAA24B4796C00EDA308 /* StatisticViewModel.swift */; }; + 9DD3BAAF24B4796C00EDA308 /* PieChartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAAB24B4796C00EDA308 /* PieChartViewModel.swift */; }; + 9DD3BAB024B4796C00EDA308 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAAC24B4796C00EDA308 /* ViewModel.swift */; }; + 9DD3BAB124B4796C00EDA308 /* DirectoryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAAD24B4796C00EDA308 /* DirectoryListViewModel.swift */; }; + 9DD3BAB724B4798E00EDA308 /* DirectoryListViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAB324B4798E00EDA308 /* DirectoryListViewModelProtocol.swift */; }; + 9DD3BAB824B4798E00EDA308 /* PieChartViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAB424B4798E00EDA308 /* PieChartViewModelProtocol.swift */; }; + 9DD3BAB924B4798E00EDA308 /* ViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAB524B4798E00EDA308 /* ViewModelProtocol.swift */; }; + 9DD3BABA24B4798E00EDA308 /* StatisticViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAB624B4798E00EDA308 /* StatisticViewModelProtocol.swift */; }; + 9DD3BABD24B479BF00EDA308 /* BytesToStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BABC24B479BF00EDA308 /* BytesToStringFormatter.swift */; }; + 9DD3BAC024B479DF00EDA308 /* PieChartSliceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BABF24B479DF00EDA308 /* PieChartSliceFactory.swift */; }; + 9DD3BAC524B479ED00EDA308 /* PieChartSubSliceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAC224B479ED00EDA308 /* PieChartSubSliceView.swift */; }; + 9DD3BAC624B479ED00EDA308 /* PieChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAC324B479ED00EDA308 /* PieChartView.swift */; }; + 9DD3BAC724B479ED00EDA308 /* PieChartSliceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAC424B479ED00EDA308 /* PieChartSliceView.swift */; }; + 9DD3BACB24B47A0400EDA308 /* PieChartSubSliceShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAC924B47A0400EDA308 /* PieChartSubSliceShape.swift */; }; + 9DD3BACC24B47A0400EDA308 /* PieChartSliceShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BACA24B47A0400EDA308 /* PieChartSliceShape.swift */; }; + 9DD3BAD224B47A1E00EDA308 /* PieChartSliceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BACE24B47A1D00EDA308 /* PieChartSliceModel.swift */; }; + 9DD3BAD324B47A1E00EDA308 /* PieChartObservableItemsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BACF24B47A1E00EDA308 /* PieChartObservableItemsModel.swift */; }; + 9DD3BAD424B47A1E00EDA308 /* PieChartItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAD024B47A1E00EDA308 /* PieChartItemModel.swift */; }; + 9DD3BAD524B47A1E00EDA308 /* PieChartSubSliceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAD124B47A1E00EDA308 /* PieChartSubSliceModel.swift */; }; + 9DD3BAD824B47A4800EDA308 /* PieChartAnySliceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAD724B47A4800EDA308 /* PieChartAnySliceProtocol.swift */; }; + 9DDFA81D24B58F47008C2C2A /* DateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DDFA81C24B58F47008C2C2A /* DateManager.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 9DCE1C0A24B582D200E49E6D /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; + 9DD3BA6C24B4639400EDA308 /* XcodeCleaner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XcodeCleaner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 9DD3BA6F24B4639400EDA308 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 9DD3BA7124B4639400EDA308 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 9DD3BA7424B4639400EDA308 /* XcodeCleaner.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = XcodeCleaner.xcdatamodel; sourceTree = ""; }; + 9DD3BA7624B4639700EDA308 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 9DD3BA7924B4639700EDA308 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 9DD3BA7C24B4639700EDA308 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 9DD3BA7E24B4639700EDA308 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9DD3BA7F24B4639700EDA308 /* XcodeCleaner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = XcodeCleaner.entitlements; sourceTree = ""; }; + 9DD3BA8924B465B800EDA308 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 9DD3BA8A24B465B800EDA308 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 9DD3BA8D24B478AE00EDA308 /* DirectoryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryManager.swift; sourceTree = ""; }; + 9DD3BA8F24B478B600EDA308 /* DirectoryType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryType.swift; sourceTree = ""; }; + 9DD3BA9224B478C900EDA308 /* DirectoryModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryModel.swift; sourceTree = ""; }; + 9DD3BA9324B478C900EDA308 /* StatisticModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticModel.swift; sourceTree = ""; }; + 9DD3BA9B24B4791900EDA308 /* DropDownView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownView.swift; sourceTree = ""; }; + 9DD3BA9C24B4791900EDA308 /* BodyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BodyView.swift; sourceTree = ""; }; + 9DD3BA9D24B4791900EDA308 /* DirectoryListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryListView.swift; sourceTree = ""; }; + 9DD3BAA124B4793900EDA308 /* FooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; + 9DD3BAA324B4794300EDA308 /* DividerButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DividerButtonView.swift; sourceTree = ""; }; + 9DD3BAA524B4794800EDA308 /* StatisticView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticView.swift; sourceTree = ""; }; + 9DD3BAA724B4794C00EDA308 /* ScanProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanProgressView.swift; sourceTree = ""; }; + 9DD3BAAA24B4796C00EDA308 /* StatisticViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticViewModel.swift; sourceTree = ""; }; + 9DD3BAAB24B4796C00EDA308 /* PieChartViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartViewModel.swift; sourceTree = ""; }; + 9DD3BAAC24B4796C00EDA308 /* ViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; + 9DD3BAAD24B4796C00EDA308 /* DirectoryListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryListViewModel.swift; sourceTree = ""; }; + 9DD3BAB324B4798E00EDA308 /* DirectoryListViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryListViewModelProtocol.swift; sourceTree = ""; }; + 9DD3BAB424B4798E00EDA308 /* PieChartViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartViewModelProtocol.swift; sourceTree = ""; }; + 9DD3BAB524B4798E00EDA308 /* ViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModelProtocol.swift; sourceTree = ""; }; + 9DD3BAB624B4798E00EDA308 /* StatisticViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticViewModelProtocol.swift; sourceTree = ""; }; + 9DD3BABC24B479BF00EDA308 /* BytesToStringFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BytesToStringFormatter.swift; sourceTree = ""; }; + 9DD3BABF24B479DF00EDA308 /* PieChartSliceFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSliceFactory.swift; sourceTree = ""; }; + 9DD3BAC224B479ED00EDA308 /* PieChartSubSliceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSubSliceView.swift; sourceTree = ""; }; + 9DD3BAC324B479ED00EDA308 /* PieChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartView.swift; sourceTree = ""; }; + 9DD3BAC424B479ED00EDA308 /* PieChartSliceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSliceView.swift; sourceTree = ""; }; + 9DD3BAC924B47A0400EDA308 /* PieChartSubSliceShape.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSubSliceShape.swift; sourceTree = ""; }; + 9DD3BACA24B47A0400EDA308 /* PieChartSliceShape.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSliceShape.swift; sourceTree = ""; }; + 9DD3BACE24B47A1D00EDA308 /* PieChartSliceModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSliceModel.swift; sourceTree = ""; }; + 9DD3BACF24B47A1E00EDA308 /* PieChartObservableItemsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartObservableItemsModel.swift; sourceTree = ""; }; + 9DD3BAD024B47A1E00EDA308 /* PieChartItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartItemModel.swift; sourceTree = ""; }; + 9DD3BAD124B47A1E00EDA308 /* PieChartSubSliceModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSubSliceModel.swift; sourceTree = ""; }; + 9DD3BAD724B47A4800EDA308 /* PieChartAnySliceProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartAnySliceProtocol.swift; sourceTree = ""; }; + 9DDFA81C24B58F47008C2C2A /* DateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateManager.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 9DD3BA6924B4639400EDA308 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9DD3BA6324B4639400EDA308 = { + isa = PBXGroup; + children = ( + 9DD3BA8A24B465B800EDA308 /* LICENSE */, + 9DD3BA8924B465B800EDA308 /* README.md */, + 9DD3BA6E24B4639400EDA308 /* XcodeCleaner */, + 9DD3BA6D24B4639400EDA308 /* Products */, + ); + sourceTree = ""; + }; + 9DD3BA6D24B4639400EDA308 /* Products */ = { + isa = PBXGroup; + children = ( + 9DD3BA6C24B4639400EDA308 /* XcodeCleaner.app */, + ); + name = Products; + sourceTree = ""; + }; + 9DD3BA6E24B4639400EDA308 /* XcodeCleaner */ = { + isa = PBXGroup; + children = ( + 9DD3BA6F24B4639400EDA308 /* AppDelegate.swift */, + 9DD3BA8F24B478B600EDA308 /* DirectoryType.swift */, + 9DD3BA8D24B478AE00EDA308 /* DirectoryManager.swift */, + 9DCE1C0A24B582D200E49E6D /* CoreDataManager.swift */, + 9DD3BA9624B478CC00EDA308 /* Views */, + 9DD3BA9124B478C100EDA308 /* Models */, + 9DD3BAA924B4795A00EDA308 /* ViewModels */, + 9DD3BAB224B4798200EDA308 /* Protocols */, + 9DD3BABB24B479B300EDA308 /* Helpers */, + 9DD3BABE24B479C900EDA308 /* PieChart */, + 9DD3BA7624B4639700EDA308 /* Assets.xcassets */, + 9DD3BA7B24B4639700EDA308 /* Main.storyboard */, + 9DD3BA7E24B4639700EDA308 /* Info.plist */, + 9DD3BA7F24B4639700EDA308 /* XcodeCleaner.entitlements */, + 9DD3BA7324B4639400EDA308 /* XcodeCleaner.xcdatamodeld */, + 9DD3BA7824B4639700EDA308 /* Preview Content */, + ); + path = XcodeCleaner; + sourceTree = ""; + }; + 9DD3BA7824B4639700EDA308 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 9DD3BA7924B4639700EDA308 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 9DD3BA9124B478C100EDA308 /* Models */ = { + isa = PBXGroup; + children = ( + 9DD3BA9224B478C900EDA308 /* DirectoryModel.swift */, + 9DD3BA9324B478C900EDA308 /* StatisticModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + 9DD3BA9624B478CC00EDA308 /* Views */ = { + isa = PBXGroup; + children = ( + 9DD3BA7124B4639400EDA308 /* ContentView.swift */, + 9DD3BA9924B478FD00EDA308 /* BodyViews */, + 9DD3BA9A24B4790200EDA308 /* FooterViews */, + ); + path = Views; + sourceTree = ""; + }; + 9DD3BA9924B478FD00EDA308 /* BodyViews */ = { + isa = PBXGroup; + children = ( + 9DD3BA9C24B4791900EDA308 /* BodyView.swift */, + 9DD3BA9D24B4791900EDA308 /* DirectoryListView.swift */, + 9DD3BA9B24B4791900EDA308 /* DropDownView.swift */, + ); + path = BodyViews; + sourceTree = ""; + }; + 9DD3BA9A24B4790200EDA308 /* FooterViews */ = { + isa = PBXGroup; + children = ( + 9DD3BAA124B4793900EDA308 /* FooterView.swift */, + 9DD3BAA324B4794300EDA308 /* DividerButtonView.swift */, + 9DD3BAA524B4794800EDA308 /* StatisticView.swift */, + 9DD3BAA724B4794C00EDA308 /* ScanProgressView.swift */, + ); + path = FooterViews; + sourceTree = ""; + }; + 9DD3BAA924B4795A00EDA308 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 9DD3BAAC24B4796C00EDA308 /* ViewModel.swift */, + 9DD3BAAD24B4796C00EDA308 /* DirectoryListViewModel.swift */, + 9DD3BAAB24B4796C00EDA308 /* PieChartViewModel.swift */, + 9DD3BAAA24B4796C00EDA308 /* StatisticViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 9DD3BAB224B4798200EDA308 /* Protocols */ = { + isa = PBXGroup; + children = ( + 9DD3BAB524B4798E00EDA308 /* ViewModelProtocol.swift */, + 9DD3BAB424B4798E00EDA308 /* PieChartViewModelProtocol.swift */, + 9DD3BAB324B4798E00EDA308 /* DirectoryListViewModelProtocol.swift */, + 9DD3BAB624B4798E00EDA308 /* StatisticViewModelProtocol.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + 9DD3BABB24B479B300EDA308 /* Helpers */ = { + isa = PBXGroup; + children = ( + 9DD3BABC24B479BF00EDA308 /* BytesToStringFormatter.swift */, + 9DDFA81C24B58F47008C2C2A /* DateManager.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + 9DD3BABE24B479C900EDA308 /* PieChart */ = { + isa = PBXGroup; + children = ( + 9DD3BABF24B479DF00EDA308 /* PieChartSliceFactory.swift */, + 9DD3BAC124B479E500EDA308 /* Views */, + 9DD3BAC824B479F300EDA308 /* Shapes */, + 9DD3BACD24B47A1000EDA308 /* Models */, + 9DD3BAD624B47A2300EDA308 /* Protocols */, + ); + path = PieChart; + sourceTree = ""; + }; + 9DD3BAC124B479E500EDA308 /* Views */ = { + isa = PBXGroup; + children = ( + 9DD3BAC424B479ED00EDA308 /* PieChartSliceView.swift */, + 9DD3BAC224B479ED00EDA308 /* PieChartSubSliceView.swift */, + 9DD3BAC324B479ED00EDA308 /* PieChartView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 9DD3BAC824B479F300EDA308 /* Shapes */ = { + isa = PBXGroup; + children = ( + 9DD3BACA24B47A0400EDA308 /* PieChartSliceShape.swift */, + 9DD3BAC924B47A0400EDA308 /* PieChartSubSliceShape.swift */, + ); + path = Shapes; + sourceTree = ""; + }; + 9DD3BACD24B47A1000EDA308 /* Models */ = { + isa = PBXGroup; + children = ( + 9DD3BAD024B47A1E00EDA308 /* PieChartItemModel.swift */, + 9DD3BACF24B47A1E00EDA308 /* PieChartObservableItemsModel.swift */, + 9DD3BACE24B47A1D00EDA308 /* PieChartSliceModel.swift */, + 9DD3BAD124B47A1E00EDA308 /* PieChartSubSliceModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + 9DD3BAD624B47A2300EDA308 /* Protocols */ = { + isa = PBXGroup; + children = ( + 9DD3BAD724B47A4800EDA308 /* PieChartAnySliceProtocol.swift */, + ); + path = Protocols; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 9DD3BA6B24B4639400EDA308 /* XcodeCleaner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9DD3BA8224B4639700EDA308 /* Build configuration list for PBXNativeTarget "XcodeCleaner" */; + buildPhases = ( + 9DD3BA6824B4639400EDA308 /* Sources */, + 9DD3BA6924B4639400EDA308 /* Frameworks */, + 9DD3BA6A24B4639400EDA308 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = XcodeCleaner; + productName = XcodeCleaner; + productReference = 9DD3BA6C24B4639400EDA308 /* XcodeCleaner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9DD3BA6424B4639400EDA308 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1150; + ORGANIZATIONNAME = "Kirill Pustovalov"; + TargetAttributes = { + 9DD3BA6B24B4639400EDA308 = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 9DD3BA6724B4639400EDA308 /* Build configuration list for PBXProject "XcodeCleaner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 9DD3BA6324B4639400EDA308; + productRefGroup = 9DD3BA6D24B4639400EDA308 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 9DD3BA6B24B4639400EDA308 /* XcodeCleaner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 9DD3BA6A24B4639400EDA308 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9DD3BA7D24B4639700EDA308 /* Main.storyboard in Resources */, + 9DD3BA7A24B4639700EDA308 /* Preview Assets.xcassets in Resources */, + 9DD3BA8B24B465B800EDA308 /* README.md in Resources */, + 9DD3BA7724B4639700EDA308 /* Assets.xcassets in Resources */, + 9DD3BA8C24B465B800EDA308 /* LICENSE in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 9DD3BA6824B4639400EDA308 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9DD3BA7224B4639400EDA308 /* ContentView.swift in Sources */, + 9DD3BA9524B478C900EDA308 /* StatisticModel.swift in Sources */, + 9DD3BAD424B47A1E00EDA308 /* PieChartItemModel.swift in Sources */, + 9DD3BAC624B479ED00EDA308 /* PieChartView.swift in Sources */, + 9DD3BA9F24B4791900EDA308 /* BodyView.swift in Sources */, + 9DD3BA9024B478B600EDA308 /* DirectoryType.swift in Sources */, + 9DD3BACC24B47A0400EDA308 /* PieChartSliceShape.swift in Sources */, + 9DD3BAA824B4794C00EDA308 /* ScanProgressView.swift in Sources */, + 9DD3BAC524B479ED00EDA308 /* PieChartSubSliceView.swift in Sources */, + 9DD3BA8E24B478AE00EDA308 /* DirectoryManager.swift in Sources */, + 9DD3BAB024B4796C00EDA308 /* ViewModel.swift in Sources */, + 9DD3BA9424B478C900EDA308 /* DirectoryModel.swift in Sources */, + 9DD3BAA224B4793900EDA308 /* FooterView.swift in Sources */, + 9DD3BAC724B479ED00EDA308 /* PieChartSliceView.swift in Sources */, + 9DD3BA9E24B4791900EDA308 /* DropDownView.swift in Sources */, + 9DD3BABD24B479BF00EDA308 /* BytesToStringFormatter.swift in Sources */, + 9DD3BABA24B4798E00EDA308 /* StatisticViewModelProtocol.swift in Sources */, + 9DD3BAAE24B4796C00EDA308 /* StatisticViewModel.swift in Sources */, + 9DD3BAD524B47A1E00EDA308 /* PieChartSubSliceModel.swift in Sources */, + 9DD3BAB924B4798E00EDA308 /* ViewModelProtocol.swift in Sources */, + 9DD3BACB24B47A0400EDA308 /* PieChartSubSliceShape.swift in Sources */, + 9DD3BAA624B4794800EDA308 /* StatisticView.swift in Sources */, + 9DD3BAA424B4794300EDA308 /* DividerButtonView.swift in Sources */, + 9DD3BA7024B4639400EDA308 /* AppDelegate.swift in Sources */, + 9DD3BAA024B4791900EDA308 /* DirectoryListView.swift in Sources */, + 9DDFA81D24B58F47008C2C2A /* DateManager.swift in Sources */, + 9DD3BAAF24B4796C00EDA308 /* PieChartViewModel.swift in Sources */, + 9DD3BAD224B47A1E00EDA308 /* PieChartSliceModel.swift in Sources */, + 9DD3BAB824B4798E00EDA308 /* PieChartViewModelProtocol.swift in Sources */, + 9DD3BAB124B4796C00EDA308 /* DirectoryListViewModel.swift in Sources */, + 9DD3BAC024B479DF00EDA308 /* PieChartSliceFactory.swift in Sources */, + 9DD3BAD324B47A1E00EDA308 /* PieChartObservableItemsModel.swift in Sources */, + 9DD3BA7524B4639400EDA308 /* XcodeCleaner.xcdatamodeld in Sources */, + 9DD3BAB724B4798E00EDA308 /* DirectoryListViewModelProtocol.swift in Sources */, + 9DCE1C0B24B582D200E49E6D /* CoreDataManager.swift in Sources */, + 9DD3BAD824B47A4800EDA308 /* PieChartAnySliceProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 9DD3BA7B24B4639700EDA308 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 9DD3BA7C24B4639700EDA308 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 9DD3BA8024B4639700EDA308 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.16; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 9DD3BA8124B4639700EDA308 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.16; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 9DD3BA8324B4639700EDA308 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = XcodeCleaner/XcodeCleaner.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"XcodeCleaner/Preview Content\""; + DEVELOPMENT_TEAM = A46C2G8GT3; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = XcodeCleaner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = com.ireldev.XcodeCleaner.XcodeCleaner; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 9DD3BA8424B4639700EDA308 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = XcodeCleaner/XcodeCleaner.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"XcodeCleaner/Preview Content\""; + DEVELOPMENT_TEAM = A46C2G8GT3; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = XcodeCleaner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = com.ireldev.XcodeCleaner.XcodeCleaner; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 9DD3BA6724B4639400EDA308 /* Build configuration list for PBXProject "XcodeCleaner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9DD3BA8024B4639700EDA308 /* Debug */, + 9DD3BA8124B4639700EDA308 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9DD3BA8224B4639700EDA308 /* Build configuration list for PBXNativeTarget "XcodeCleaner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9DD3BA8324B4639700EDA308 /* Debug */, + 9DD3BA8424B4639700EDA308 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 9DD3BA7324B4639400EDA308 /* XcodeCleaner.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 9DD3BA7424B4639400EDA308 /* XcodeCleaner.xcdatamodel */, + ); + currentVersion = 9DD3BA7424B4639400EDA308 /* XcodeCleaner.xcdatamodel */; + path = XcodeCleaner.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = 9DD3BA6424B4639400EDA308 /* Project object */; +} diff --git a/XcodeCleaner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/XcodeCleaner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..b9aaed9 --- /dev/null +++ b/XcodeCleaner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/XcodeCleaner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/XcodeCleaner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/XcodeCleaner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/XcodeCleaner.xcodeproj/xcuserdata/revilarva.xcuserdatad/xcschemes/xcschememanagement.plist b/XcodeCleaner.xcodeproj/xcuserdata/revilarva.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..a5817c0 --- /dev/null +++ b/XcodeCleaner.xcodeproj/xcuserdata/revilarva.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + XcodeCleaner.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/XcodeCleaner/AppDelegate.swift b/XcodeCleaner/AppDelegate.swift new file mode 100644 index 0000000..a827def --- /dev/null +++ b/XcodeCleaner/AppDelegate.swift @@ -0,0 +1,137 @@ +// +// AppDelegate.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 07.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Cocoa +import SwiftUI + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + var window: NSWindow! + + + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath. + // Add `@Environment(\.managedObjectContext)` in the views that will need the context. + let viewModel = ViewModel() + let contentView = ContentView().environment(\.managedObjectContext, persistentContainer.viewContext).environmentObject(viewModel) + + // Create the window and set the content view. + window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, defer: false) + window.center() + window.setFrameAutosaveName("Main Window") + window.contentView = NSHostingView(rootView: contentView) + window.makeKeyAndOrderFront(nil) + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + // MARK: - Core Data stack + + lazy var persistentContainer: NSPersistentContainer = { + /* + The persistent container for the application. This implementation + creates and returns a container, having loaded the store for the + application to it. This property is optional since there are legitimate + error conditions that could cause the creation of the store to fail. + */ + let container = NSPersistentContainer(name: "XcodeCleaner") + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error)") + } + }) + return container + }() + + // MARK: - Core Data Saving and Undo support + + @IBAction func saveAction(_ sender: AnyObject?) { + // Performs the save action for the application, which is to send the save: message to the application's managed object context. Any encountered errors are presented to the user. + let context = persistentContainer.viewContext + + if !context.commitEditing() { + NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing before saving") + } + if context.hasChanges { + do { + try context.save() + } catch { + // Customize this code block to include application-specific recovery steps. + let nserror = error as NSError + NSApplication.shared.presentError(nserror) + } + } + } + + func windowWillReturnUndoManager(window: NSWindow) -> UndoManager? { + // Returns the NSUndoManager for the application. In this case, the manager returned is that of the managed object context for the application. + return persistentContainer.viewContext.undoManager + } + + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { + // Save changes in the application's managed object context before the application terminates. + let context = persistentContainer.viewContext + + if !context.commitEditing() { + NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing to terminate") + return .terminateCancel + } + + if !context.hasChanges { + return .terminateNow + } + + do { + try context.save() + } catch { + let nserror = error as NSError + + // Customize this code block to include application-specific recovery steps. + let result = sender.presentError(nserror) + if (result) { + return .terminateCancel + } + + let question = NSLocalizedString("Could not save changes while quitting. Quit anyway?", comment: "Quit without saves error question message") + let info = NSLocalizedString("Quitting now will lose any changes you have made since the last successful save", comment: "Quit without saves error question info"); + let quitButton = NSLocalizedString("Quit anyway", comment: "Quit anyway button title") + let cancelButton = NSLocalizedString("Cancel", comment: "Cancel button title") + let alert = NSAlert() + alert.messageText = question + alert.informativeText = info + alert.addButton(withTitle: quitButton) + alert.addButton(withTitle: cancelButton) + + let answer = alert.runModal() + if answer == .alertSecondButtonReturn { + return .terminateCancel + } + } + // If we got here, it is time to quit. + return .terminateNow + } + +} + diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/Contents.json b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..6adbbd7 --- /dev/null +++ b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "filename" : "mac-16x16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "mac-16x16@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "mac-32x32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "mac-32x32@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "mac-128x128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "mac-128x128@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "mac-256x256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "mac-256x256@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "mac-512x512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "mac-512x512@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128.png new file mode 100644 index 0000000..b552560 Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128.png differ diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128@2x.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128@2x.png new file mode 100644 index 0000000..f003af9 Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128@2x.png differ diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16.png new file mode 100644 index 0000000..091a722 Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16.png differ diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16@2x.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16@2x.png new file mode 100644 index 0000000..13628b2 Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16@2x.png differ diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256.png new file mode 100644 index 0000000..f003af9 Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256.png differ diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256@2x.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256@2x.png new file mode 100644 index 0000000..5622065 Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256@2x.png differ diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32.png new file mode 100644 index 0000000..13628b2 Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32.png differ diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32@2x.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32@2x.png new file mode 100644 index 0000000..2587a8d Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32@2x.png differ diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512.png new file mode 100644 index 0000000..5622065 Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512.png differ diff --git a/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512@2x.png b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512@2x.png new file mode 100644 index 0000000..b6ee3f4 Binary files /dev/null and b/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512@2x.png differ diff --git a/XcodeCleaner/Assets.xcassets/Contents.json b/XcodeCleaner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/XcodeCleaner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/XcodeCleaner/Base.lproj/Main.storyboard b/XcodeCleaner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..57e74f8 --- /dev/null +++ b/XcodeCleaner/Base.lproj/Main.storyboard @@ -0,0 +1,683 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XcodeCleaner/CoreDataManager.swift b/XcodeCleaner/CoreDataManager.swift new file mode 100644 index 0000000..4213356 --- /dev/null +++ b/XcodeCleaner/CoreDataManager.swift @@ -0,0 +1,76 @@ +// +// CoreDataManager.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 08.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import AppKit +import CoreData + +struct CoreDataManager { + static let shared = CoreDataManager() + + func getStatistic() -> StatisticModel { + var statisticModel = StatisticModel() + fetchStatisticIn(statisticModel: &statisticModel) + + return statisticModel + } + func saveStatistic(statistic: StatisticModel) { + let newTotalCleanedValue = statistic.totalCleaned + let newDate = statistic.lastTimeCleaned + + saveStatistic(newValue: newTotalCleanedValue, newDate: newDate) + } + private func fetchStatisticIn(statisticModel: inout StatisticModel) { + guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return } + + let managedContext = appDelegate.persistentContainer.viewContext + let fetchRequest = NSFetchRequest(entityName: "StatisticEntity") + + do { + let fetchRequestResults = try managedContext.fetch(fetchRequest) + + let totalCleaned = fetchRequestResults.first?.value(forKey: "totalSize") as? Int64 + let date = fetchRequestResults.first?.value(forKey: "date") as? Date + + let modelFromFetchRequest = StatisticModel(totalCleaned: totalCleaned, lastTimeCleaned: date) + statisticModel = modelFromFetchRequest + + } catch { + print("could not fetch statistic \(error.localizedDescription)") + } + } + private func saveStatistic(newValue: Int64?, newDate: Date?) { + guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return } + let managedContext = appDelegate.persistentContainer.viewContext + let entity = NSEntityDescription.entity(forEntityName: "StatisticEntity", in: managedContext)! + + let fetchRequest = NSFetchRequest(entityName: "StatisticEntity") + + do { + let fetchRequestResults = try managedContext.fetch(fetchRequest) + if fetchRequestResults.count == 0 { + let statistic = NSManagedObject(entity: entity, insertInto: managedContext) + + statistic.setValue(newValue, forKeyPath: "totalSize") + statistic.setValue(newDate, forKeyPath: "date") + } else { + let previousStatistic = fetchRequestResults.first! + + let previousTotalSizeValue = previousStatistic.value(forKey: "totalSize") as! Int64 + let value = (newValue ?? 0) + previousTotalSizeValue + + previousStatistic.setValue(newDate, forKey: "date") + previousStatistic.setValue(value, forKey: "totalSize") + } + + try managedContext.save() + } catch { + print("Error, could not save statistic \(error.localizedDescription)") + } + } +} + diff --git a/XcodeCleaner/DirectoryManager.swift b/XcodeCleaner/DirectoryManager.swift new file mode 100644 index 0000000..857905a --- /dev/null +++ b/XcodeCleaner/DirectoryManager.swift @@ -0,0 +1,130 @@ +// +// DirectoryManager.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 04.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Cocoa + +struct DirectoryManager { + func getValidHomeDirectory() -> String { + var homeDirectory = NSHomeDirectory() + let prefix = "/Library/Containers/" + + if homeDirectory.contains(prefix) { + if let range = homeDirectory.range(of: prefix) { + homeDirectory = String(homeDirectory[.. String { + let homeDirectory = getValidHomeDirectory() + let xcodePath = "/Library/Developer/Xcode/" + return "\(homeDirectory + xcodePath)" + } + func getDerivedDataPath() -> String { + let xcodePath = getXcodeDefaultPath() + let derivedDataPath = "DerivedData/" + return "\(xcodePath + derivedDataPath)" + } + func getDeviceSupportPath() -> String { + let xcodePath = getXcodeDefaultPath() + let deviceSupportPath = "iOS DeviceSupport/" + return "\(xcodePath + deviceSupportPath)" + } + func getArchivesPath() -> String { + let xcodePath = getXcodeDefaultPath() + let deviceSupportPath = "Archives/" + return "\(xcodePath + deviceSupportPath)" + } + func getSubDirectoriesForPath(path: String) -> [String] { + let fileManager = FileManager.default + + var subDirectories: [String] = [] + do { + let directories = try fileManager.contentsOfDirectory(atPath: path) + + for directory in directories { + let subDirectoryPath = path + directory + subDirectories.append(subDirectoryPath) + } + } catch { + print(error.localizedDescription) + } + + return subDirectories + } + func getDirectorySize(path: String, completion: @escaping () -> Void = { }) -> Int64 { + let fileManager = FileManager.default + var directorySize: Int64 = 0 + + let directories = fileManager.subpaths(atPath: path) + guard directories != nil else { + completion() + return 0 + } + + for directory in directories! { + do { + let attributes = try fileManager.attributesOfItem(atPath: path + directory) + directorySize += attributes[FileAttributeKey.size] as! Int64 + } catch { + print(error.localizedDescription) + } + } + completion() + + return directorySize + } + func normalizeDirectoryPath(directory: String) -> String { + var newDirectoryPath = directory + + if !newDirectoryPath.hasSuffix("/") { + newDirectoryPath += "/" + } + return newDirectoryPath + } + func normalizeDirectoryPathForDisplay(directory: String, forType type: DirectoryType) -> String { + var result = directory + var prefix: String + + switch type { + case .derivedData: + prefix = getDerivedDataPath() + case .deviceSupport: + prefix = getDeviceSupportPath() + case .archives: + prefix = getArchivesPath() + } + + if directory.contains(prefix) { + result = directory.replacingOccurrences(of: prefix, with: "") + } + + return result + } + func cleanDirectory(forType type: DirectoryType) { + let fileManager = FileManager.default + + var directoryPath: String + switch type { + case .derivedData: + directoryPath = getDerivedDataPath() + case .deviceSupport: + directoryPath = getDeviceSupportPath() + case .archives: + directoryPath = getArchivesPath() + } + + let directoryURL = URL(fileURLWithPath: normalizeDirectoryPath(directory: directoryPath)) + + do { + try fileManager.removeItem(at: directoryURL) + } catch { + print(error.localizedDescription) + } + } +} diff --git a/XcodeCleaner/DirectoryType.swift b/XcodeCleaner/DirectoryType.swift new file mode 100644 index 0000000..fd23be4 --- /dev/null +++ b/XcodeCleaner/DirectoryType.swift @@ -0,0 +1,15 @@ +// +// DirectoryType.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 05.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation + +public enum DirectoryType { + case derivedData + case deviceSupport + case archives +} diff --git a/XcodeCleaner/Helpers/BytesToStringFormatter.swift b/XcodeCleaner/Helpers/BytesToStringFormatter.swift new file mode 100644 index 0000000..56dc1b8 --- /dev/null +++ b/XcodeCleaner/Helpers/BytesToStringFormatter.swift @@ -0,0 +1,18 @@ +// +// BytesToStringFormatter.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 06.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation + +struct BytesToStringFormatter { + static func format(size: Int64, allowedUnits: ByteCountFormatter.Units = [.useGB, .useMB]) -> String { + let byteCountFormatter = ByteCountFormatter() + byteCountFormatter.allowedUnits = allowedUnits + + return byteCountFormatter.string(fromByteCount: size) + } +} diff --git a/XcodeCleaner/Helpers/DateManager.swift b/XcodeCleaner/Helpers/DateManager.swift new file mode 100644 index 0000000..a2355c0 --- /dev/null +++ b/XcodeCleaner/Helpers/DateManager.swift @@ -0,0 +1,21 @@ +// +// DateManager.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 08.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation + +struct DateManager { + static func getCurrentDate() -> Date { + Date() + } + static func getStringDate(date: Date) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .medium + dateFormatter.timeStyle = .none + return dateFormatter.string(from: date) + } +} diff --git a/XcodeCleaner/Info.plist b/XcodeCleaner/Info.plist new file mode 100644 index 0000000..c68cd26 --- /dev/null +++ b/XcodeCleaner/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSApplicationCategoryType + public.app-category.developer-tools + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2020 Kirill Pustovalov. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + NSSupportsAutomaticTermination + + NSSupportsSuddenTermination + + + diff --git a/XcodeCleaner/Models/DirectoryModel.swift b/XcodeCleaner/Models/DirectoryModel.swift new file mode 100644 index 0000000..a0e3fc2 --- /dev/null +++ b/XcodeCleaner/Models/DirectoryModel.swift @@ -0,0 +1,14 @@ +// +// DirectoryModel.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 04.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation + +struct DirectoryModel { + var name: String + var size: Int64 +} diff --git a/XcodeCleaner/Models/StatisticModel.swift b/XcodeCleaner/Models/StatisticModel.swift new file mode 100644 index 0000000..ce49743 --- /dev/null +++ b/XcodeCleaner/Models/StatisticModel.swift @@ -0,0 +1,14 @@ +// +// StatisticModel.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 06.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation + +struct StatisticModel { + var totalCleaned: Int64? + var lastTimeCleaned: Date? +} diff --git a/XcodeCleaner/PieChart/Models/PieChartItemModel.swift b/XcodeCleaner/PieChart/Models/PieChartItemModel.swift new file mode 100644 index 0000000..7343dab --- /dev/null +++ b/XcodeCleaner/PieChart/Models/PieChartItemModel.swift @@ -0,0 +1,22 @@ +// +// PieChartItemModel.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +public struct PieChartItemModel { + public var value: Double + public var color: Color + public var subItems: [PieChartItemModel] + + public init(value: Double, color: Color, subItems: [PieChartItemModel] = []) { + self.value = value + self.color = color + + self.subItems = subItems + } +} diff --git a/XcodeCleaner/PieChart/Models/PieChartObservableItemsModel.swift b/XcodeCleaner/PieChart/Models/PieChartObservableItemsModel.swift new file mode 100644 index 0000000..ddbc7c8 --- /dev/null +++ b/XcodeCleaner/PieChart/Models/PieChartObservableItemsModel.swift @@ -0,0 +1,21 @@ +// +// PieChartObservableItemsModel.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +public class PCItems: ObservableObject { + @Published public var items: [PieChartItemModel] + public init(items: [PieChartItemModel]) { + self.items = items + } + public init(data: [Double], chartColor: Color) { + self.items = data.map { + PieChartItemModel(value: $0, color: chartColor) + } + } +} diff --git a/XcodeCleaner/PieChart/Models/PieChartSliceModel.swift b/XcodeCleaner/PieChart/Models/PieChartSliceModel.swift new file mode 100644 index 0000000..95af123 --- /dev/null +++ b/XcodeCleaner/PieChart/Models/PieChartSliceModel.swift @@ -0,0 +1,29 @@ +// +// PieChartSliceModel.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct PieChartSliceModel: PieChartAnySliceProtocol { + var value: Double + var color: Color + + var startDegree: Double + var endDegree: Double + + var subSlices: [PieChartSubSliceModel] + + public init(value: Double, color: Color, startDegree: Double, endDegree: Double, subSlices: [PieChartSubSliceModel] = []) { + self.value = value + self.color = color + + self.startDegree = startDegree + self.endDegree = endDegree + self.subSlices = subSlices + } +} + diff --git a/XcodeCleaner/PieChart/Models/PieChartSubSliceModel.swift b/XcodeCleaner/PieChart/Models/PieChartSubSliceModel.swift new file mode 100644 index 0000000..33bebce --- /dev/null +++ b/XcodeCleaner/PieChart/Models/PieChartSubSliceModel.swift @@ -0,0 +1,26 @@ +// +// PieChartSubSliceModel.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct PieChartSubSliceModel: PieChartAnySliceProtocol { + var value: Double + var color: Color + + var startDegree: Double + var endDegree: Double + + public init(value: Double, color: Color, startDegree: Double, endDegree: Double) { + self.value = value + self.color = color + + self.startDegree = startDegree + self.endDegree = endDegree + } +} + diff --git a/XcodeCleaner/PieChart/PieChartSliceFactory.swift b/XcodeCleaner/PieChart/PieChartSliceFactory.swift new file mode 100644 index 0000000..da3802f --- /dev/null +++ b/XcodeCleaner/PieChart/PieChartSliceFactory.swift @@ -0,0 +1,55 @@ +// +// PieChartSliceFactory.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +class PieChartSliceFactory { + func createPieChartSlicesFromItems(items: [PieChartItemModel], maxShapeDegree: Double, initialDegree: Double = 0.0) -> [PieChartSliceModel] { + var slices: [PieChartSliceModel] = [] + var previousSliceEndDegree = initialDegree + + let maxSumSliceValue = items.reduce(0) { $0 + $1.value } + + for itemIndex in 0 ..< items.count { + let item = items[itemIndex] + + let sliceStartDegree = previousSliceEndDegree + let proportionalValue = item.value / maxSumSliceValue + + let sliceEndDegree = sliceStartDegree + proportionalValue * maxShapeDegree + + var subslices: [PieChartSubSliceModel] = [] + if item.subItems.count > 0 { + let sliceIndex = itemIndex + 1 + subslices = createPieChartSubSlicesFromItems(items: item.subItems, initialDegree: previousSliceEndDegree, maxShapeDegree: sliceEndDegree, withIndex: sliceIndex) + } + let slice = PieChartSliceModel(value: item.value, color: item.color, startDegree: sliceStartDegree, endDegree: sliceEndDegree, subSlices: subslices) + slices.append(slice) + previousSliceEndDegree = sliceEndDegree + } + return slices + } + func createPieChartSubSlicesFromItems(items: [PieChartItemModel], initialDegree: Double, maxShapeDegree: Double, withIndex sliceIndex: Int = 1) -> [PieChartSubSliceModel] { + var subSlices: [PieChartSubSliceModel] = [] + var previousSliceEndDegree = initialDegree + + let maxSumSliceValue = items.reduce(0) { $0 + $1.value } + + for itemIndex in 0 ..< items.count { + let item = items[itemIndex] + + let sliceStartDegree = previousSliceEndDegree + let proportionalValue = item.value / maxSumSliceValue + + let sliceEndDegree = sliceStartDegree + proportionalValue * maxShapeDegree / Double(sliceIndex) + + let slice = PieChartSubSliceModel(value: item.value, color: item.color, startDegree: sliceStartDegree, endDegree: sliceEndDegree) + subSlices.append(slice) + previousSliceEndDegree = sliceEndDegree + } + return subSlices + } +} diff --git a/XcodeCleaner/PieChart/Protocols/PieChartAnySliceProtocol.swift b/XcodeCleaner/PieChart/Protocols/PieChartAnySliceProtocol.swift new file mode 100644 index 0000000..44ac5c2 --- /dev/null +++ b/XcodeCleaner/PieChart/Protocols/PieChartAnySliceProtocol.swift @@ -0,0 +1,18 @@ +// +// PieChartAnySliceProtocol.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +protocol PieChartAnySliceProtocol { + var value: Double { get set } + var color: Color { get set } + + var startDegree: Double { get set } + var endDegree: Double { get set } +} + diff --git a/XcodeCleaner/PieChart/Shapes/PieChartSliceShape.swift b/XcodeCleaner/PieChart/Shapes/PieChartSliceShape.swift new file mode 100644 index 0000000..c8c2f21 --- /dev/null +++ b/XcodeCleaner/PieChart/Shapes/PieChartSliceShape.swift @@ -0,0 +1,42 @@ +// +// PieChartSliceShape.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct PieChartSliceShape: Shape { + var startAngle: Angle + var endAngle: Angle + + func path(in rect: CGRect) -> Path { + var path = Path() + let center = CGPoint(x: rect.midX, y: rect.midY) + let size = min(rect.width / 2, rect.height / 2) + + let radius = size + + path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false) + path.addLine(to: center) + + path.closeSubpath() + + return path + } +} + +struct PieChartSliceShape_Previews: PreviewProvider { + static var previews: some View { + let startAngle = Angle(degrees: 0) + let endAngle = Angle(degrees: 90) + + return PieChartSliceShape(startAngle: startAngle, endAngle: endAngle) + .fill() + .foregroundColor(.orange) + .frame(width: 150, height: 150) + } +} + diff --git a/XcodeCleaner/PieChart/Shapes/PieChartSubSliceShape.swift b/XcodeCleaner/PieChart/Shapes/PieChartSubSliceShape.swift new file mode 100644 index 0000000..787273e --- /dev/null +++ b/XcodeCleaner/PieChart/Shapes/PieChartSubSliceShape.swift @@ -0,0 +1,41 @@ +// +// PieChartSubSliceShape.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct PieChartSubSliceShape: Shape { + var startAngle: Angle + var endAngle: Angle + + func path(in rect: CGRect) -> Path { + var path = Path() + let center = CGPoint(x: rect.midX, y: rect.midY) + let size = min(rect.width / 2, rect.height / 2) + + let firstRadius = size + let secondRadius = size / 2 + + path.addArc(center: center, radius: firstRadius, startAngle: startAngle, endAngle: endAngle, clockwise: false) + path.addArc(center: center, radius: secondRadius, startAngle: endAngle, endAngle: startAngle, clockwise: true) + path.closeSubpath() + + return path + } +} + +struct PieChartSubSliceShape_Previews: PreviewProvider { + static var previews: some View { + let startAngle = Angle(degrees: 0) + let endAngle = Angle(degrees: 90) + + return PieChartSubSliceShape(startAngle: startAngle, endAngle: endAngle) + .fill() + .foregroundColor(.orange) + .frame(width: 150, height: 150) + } +} diff --git a/XcodeCleaner/PieChart/Views/PieChartSliceView.swift b/XcodeCleaner/PieChart/Views/PieChartSliceView.swift new file mode 100644 index 0000000..9b570b8 --- /dev/null +++ b/XcodeCleaner/PieChart/Views/PieChartSliceView.swift @@ -0,0 +1,57 @@ +// +// PieChartSliceView.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct PieChartSliceView: View { + var rect: CGRect + + let slice: PieChartSliceModel + let sliceSeparatorColor: Color + + @State var isVisible: Bool = false + + var body: some View { + let startAngle = Angle(degrees: slice.startDegree) + let endAngle = Angle(degrees: slice.endDegree) + + let sliceShape = PieChartSliceShape(startAngle: startAngle, endAngle: endAngle) + + return Group { sliceShape + .fill() + .overlay(sliceShape.stroke(sliceSeparatorColor, lineWidth: 2)) + .foregroundColor(slice.color) + .scaleEffect(isVisible ? 1: 0) + .animation(Animation.easeIn) + .onAppear { + self.isVisible.toggle() + } + if self.slice.subSlices.count > 0 { + GeometryReader { geometryReader in + ForEach(0 ..< self.slice.subSlices.count, id: \.self) { subSliceIndex in + PieChartSubSliceView(rect: geometryReader.frame(in: .local), subSlice: self.slice.subSlices[subSliceIndex], sliceSeparatorColor: self.sliceSeparatorColor) + .padding(0 - min(geometryReader.size.width, geometryReader.size.height) / 2) + } + } + } + } + } +} + +struct PieSliceView_Previews: PreviewProvider { + static var previews: some View { + let pieSlice = PieChartSliceModel(value: .zero, color: .orange, startDegree: 50, endDegree: 130) + let sliceSeparatorColor = Color.black + + return GeometryReader { geometryReader in + PieChartSliceView(rect: geometryReader.frame(in: .local), slice: pieSlice, sliceSeparatorColor: sliceSeparatorColor) + } + .frame(width: 150, height: 150) + } +} + diff --git a/XcodeCleaner/PieChart/Views/PieChartSubSliceView.swift b/XcodeCleaner/PieChart/Views/PieChartSubSliceView.swift new file mode 100644 index 0000000..c0d1883 --- /dev/null +++ b/XcodeCleaner/PieChart/Views/PieChartSubSliceView.swift @@ -0,0 +1,48 @@ +// +// PieChartSubSliceView.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct PieChartSubSliceView: View { + var rect: CGRect + + let subSlice: PieChartSubSliceModel + let sliceSeparatorColor: Color + + @State var isVisible: Bool = false + + var body: some View { + let startAngle = Angle(degrees: subSlice.startDegree) + let endAngle = Angle(degrees: subSlice.endDegree) + + let sliceShape = PieChartSubSliceShape(startAngle: startAngle, endAngle: endAngle) + + return Group { sliceShape + .fill() + .overlay(sliceShape.stroke(sliceSeparatorColor, lineWidth: 2)) + .foregroundColor(subSlice.color) + .scaleEffect(isVisible ? 1: 0) + .animation(Animation.easeIn) + .onAppear { + self.isVisible.toggle() + } + } + } +} + +struct PieChartSubSliceView_Previews: PreviewProvider { + static var previews: some View { + let pieSlice = PieChartSubSliceModel(value: .zero, color: .orange, startDegree: 50, endDegree: 130) + let sliceSeparatorColor = Color.black + + return GeometryReader { geometryReader in + PieChartSubSliceView(rect: geometryReader.frame(in: .local), subSlice: pieSlice, sliceSeparatorColor: sliceSeparatorColor) + } + .frame(width: 150, height: 150) + } +} diff --git a/XcodeCleaner/PieChart/Views/PieChartView.swift b/XcodeCleaner/PieChart/Views/PieChartView.swift new file mode 100644 index 0000000..cc21a20 --- /dev/null +++ b/XcodeCleaner/PieChart/Views/PieChartView.swift @@ -0,0 +1,69 @@ +// +// PieChartView.swift +// PieChartSwiftUI +// +// Created by Kirill Pustovalov on 28.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +public struct PieChartView: View { + @ObservedObject var items: PCItems + var sliceSeparatorColor: Color + + public init(items: PCItems, sliceSeparatorColor: Color = .white) { + self.items = items + self.sliceSeparatorColor = sliceSeparatorColor + } + + public var body: some View { + let sliceFactory = PieChartSliceFactory() + let circleShapeMaxDegree = 360.0 + + let slices = sliceFactory.createPieChartSlicesFromItems(items: items.items, maxShapeDegree: circleShapeMaxDegree) + + return Group { + GeometryReader { geometryReader in + if slices.count == 1 { + Circle() + .fill(slices.first!.color) + .overlay(Circle().stroke(self.sliceSeparatorColor, lineWidth: 1)) + .frame(width: geometryReader.size.width, height: geometryReader.size.height) + + ForEach(0 ..< slices.first!.subSlices.count, id: \.self) { subSliceIndex in + PieChartSubSliceView(rect: geometryReader.frame(in: .local), subSlice: (slices.first!.subSlices[subSliceIndex]), sliceSeparatorColor: self.sliceSeparatorColor) + } + } else { + ForEach(0 ..< slices.count, id: \.self) { sliceIndex in + PieChartSliceView(rect: geometryReader.frame(in: .local), slice: slices[sliceIndex], sliceSeparatorColor: self.sliceSeparatorColor) + } + } + } + } + } +} + +struct PieChartView_Previews: PreviewProvider { + static var previews: some View { + let pieChartItemOne = PieChartItemModel(value: 25, color: .pink) + let pieChartItemTwo = PieChartItemModel(value: 25, color: .orange) + let pieChartItemThree = PieChartItemModel(value: 25, color: .red) + + let items = PCItems(items: [pieChartItemOne, pieChartItemTwo, pieChartItemThree]) + let itemsFromData = PCItems(data: [25, 50, 25], chartColor: .black) + + return VStack { + Spacer() + + PieChartView(items: items, sliceSeparatorColor: .black) + .frame(width: 100, height: 100, alignment: .center) + Spacer() + + PieChartView(items: itemsFromData, sliceSeparatorColor: .white) + .frame(width: 100, height: 100, alignment: .center) + Spacer() + + } + } +} diff --git a/XcodeCleaner/Preview Content/Preview Assets.xcassets/Contents.json b/XcodeCleaner/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/XcodeCleaner/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/XcodeCleaner/Protocols/DirectoryListViewModelProtocol.swift b/XcodeCleaner/Protocols/DirectoryListViewModelProtocol.swift new file mode 100644 index 0000000..928ddc2 --- /dev/null +++ b/XcodeCleaner/Protocols/DirectoryListViewModelProtocol.swift @@ -0,0 +1,18 @@ +// +// DirectoryListViewModelProtocol.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 06.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +protocol DirectoryListViewModelProtocol { + var directoryName: String { get set } + var totalSize: Int64 { get set } + var directories: [DirectoryModel] { get set } + var circleColor: Color { get set } + + mutating func calculateTotalSize() +} diff --git a/XcodeCleaner/Protocols/PieChartViewModelProtocol.swift b/XcodeCleaner/Protocols/PieChartViewModelProtocol.swift new file mode 100644 index 0000000..7611bb9 --- /dev/null +++ b/XcodeCleaner/Protocols/PieChartViewModelProtocol.swift @@ -0,0 +1,16 @@ +// +// PieChartViewModelProtocol.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 07.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation + +protocol PieChartViewModelProtocol { + var items: [PieChartItemModel] { get set } + + mutating func createItems(derivedData: [DirectoryModel], deviceSupport: [DirectoryModel], archives: [DirectoryModel]) + func getPCItems() -> PCItems +} diff --git a/XcodeCleaner/Protocols/StatisticViewModelProtocol.swift b/XcodeCleaner/Protocols/StatisticViewModelProtocol.swift new file mode 100644 index 0000000..2aa50b9 --- /dev/null +++ b/XcodeCleaner/Protocols/StatisticViewModelProtocol.swift @@ -0,0 +1,16 @@ +// +// StatisticViewModelProtocol.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 06.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation + +protocol StatisticViewModelProtocol { + var statistic: StatisticModel { get set } + + func getLastDate() -> String + func getTotalSize() -> String +} diff --git a/XcodeCleaner/Protocols/ViewModelProtocol.swift b/XcodeCleaner/Protocols/ViewModelProtocol.swift new file mode 100644 index 0000000..73d2f91 --- /dev/null +++ b/XcodeCleaner/Protocols/ViewModelProtocol.swift @@ -0,0 +1,36 @@ +// +// ViewModelProtocol.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 06.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation + +protocol ViewModelProtocol { + var isScanStarted: Bool { get set } + + var directoriesCount: Int { get set } + var analyzedDirectoriesCount: Int { get set } + var totalSize: Int64 { get set } + + var derivedData: [DirectoryModel] { get set } + var deviceSupport: [DirectoryModel] { get set } + var archives: [DirectoryModel] { get set } + + var scanProgress: Double { get } + + var isReadyToBeCleaned: Bool { get set } + + var isAlertPresented: Bool { get set } + + func startScan() + func calculateSize(ofDirectory: inout [DirectoryModel], subDirectories: [String], type: DirectoryType) + func getViewModelForItemList(forType type: DirectoryType) -> DirectoryListViewModelProtocol + func cleanBeforeScan() + func getViewModelForPieChart() -> PieChartViewModelProtocol + func getViewModelForStatistic() -> StatisticViewModelProtocol + + func startClean() +} diff --git a/XcodeCleaner/ViewModels/DirectoryListViewModel.swift b/XcodeCleaner/ViewModels/DirectoryListViewModel.swift new file mode 100644 index 0000000..fb316fb --- /dev/null +++ b/XcodeCleaner/ViewModels/DirectoryListViewModel.swift @@ -0,0 +1,26 @@ +// +// DirectoryListViewModel.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 06.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct DirectoryListViewModel: DirectoryListViewModelProtocol { + var directoryName: String + var directories: [DirectoryModel] + + var totalSize: Int64 = 0 + var circleColor: Color + + mutating func calculateTotalSize() { + var size: Int64 = 0 + + directories.forEach { directory in + size += directory.size + } + totalSize = size + } +} diff --git a/XcodeCleaner/ViewModels/PieChartViewModel.swift b/XcodeCleaner/ViewModels/PieChartViewModel.swift new file mode 100644 index 0000000..933a1c7 --- /dev/null +++ b/XcodeCleaner/ViewModels/PieChartViewModel.swift @@ -0,0 +1,68 @@ +// +// PieChartViewModel.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 07.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct PieChartViewModel: PieChartViewModelProtocol { + var items: [PieChartItemModel] = [] + + mutating func createItems(derivedData: [DirectoryModel], deviceSupport: [DirectoryModel], archives: [DirectoryModel]) { + var derivedDataSize: Int64 = 0 + derivedData.forEach { + derivedDataSize += $0.size + } + + var deviceSupportSize: Int64 = 0 + deviceSupport.forEach { + deviceSupportSize += $0.size + } + + var archivesSize: Int64 = 0 + archives.forEach { + archivesSize += $0.size + } + + var derivedData: PieChartItemModel + var deviceSupport: PieChartItemModel + var archives: PieChartItemModel + + if derivedDataSize == 0 && deviceSupportSize == 0 && archivesSize == 0 { + let defaultValue = 1.0 + + derivedData = PieChartItemModel(value: defaultValue, color: .pink) + deviceSupport = PieChartItemModel(value: defaultValue, color: Color(.cyan)) + archives = PieChartItemModel(value: defaultValue, color: .orange) + + let items = [archives, deviceSupport, derivedData] + self.items = items + } else { + derivedData = PieChartItemModel(value: Double(derivedDataSize), color: .pink) + deviceSupport = PieChartItemModel(value: Double(deviceSupportSize), color: Color(.cyan)) + archives = PieChartItemModel(value: Double(archivesSize), color: .orange) + + let items = [archives, deviceSupport, derivedData] + + var normalizedItems: [PieChartItemModel] = [] + + items.forEach { + if $0.value != 0 { + normalizedItems.append($0) + } + } + self.items = normalizedItems + } + } + func getPCItems() -> PCItems { + let pcItems = PCItems(items: []) + + items.forEach { item in + pcItems.items.append(item) + } + return pcItems + } +} diff --git a/XcodeCleaner/ViewModels/StatisticViewModel.swift b/XcodeCleaner/ViewModels/StatisticViewModel.swift new file mode 100644 index 0000000..df58d19 --- /dev/null +++ b/XcodeCleaner/ViewModels/StatisticViewModel.swift @@ -0,0 +1,22 @@ +// +// StatisticViewModel.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 06.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation + +struct StatisticViewModel: StatisticViewModelProtocol { + var statistic: StatisticModel + + func getLastDate() -> String { + guard let date = statistic.lastTimeCleaned else { return "none" } + return DateManager.getStringDate(date: date) + } + func getTotalSize() -> String { + guard let size = statistic.totalCleaned else { return "none" } + return BytesToStringFormatter.format(size: size) + } +} diff --git a/XcodeCleaner/ViewModels/ViewModel.swift b/XcodeCleaner/ViewModels/ViewModel.swift new file mode 100644 index 0000000..5ccf4cb --- /dev/null +++ b/XcodeCleaner/ViewModels/ViewModel.swift @@ -0,0 +1,145 @@ +// +// ViewModel.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 05.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import Foundation +import SwiftUI + +class ViewModel: ObservableObject, ViewModelProtocol { + private let directoryManager = DirectoryManager() + + @Published var isScanStarted = false + + @Published var directoriesCount: Int = 0 + @Published var analyzedDirectoriesCount: Int = 0 + @Published var totalSize: Int64 = 0 + + @Published var derivedData: [DirectoryModel] = [] + @Published var deviceSupport: [DirectoryModel] = [] + @Published var archives: [DirectoryModel] = [] + + @Published var isReadyToBeCleaned = false + + @Published var isAlertPresented = false + + var scanProgress: Double { + directoriesCount == 0 ? 0: Double(analyzedDirectoriesCount) / Double(directoriesCount) + } + func startScan() { + guard !isScanStarted else { return } + cleanBeforeScan() + + isScanStarted.toggle() + + let derivedDataDirectories = directoryManager.getSubDirectoriesForPath(path: directoryManager.getDerivedDataPath()) + directoriesCount += derivedDataDirectories.count + + let deviceSupportDirectories = directoryManager.getSubDirectoriesForPath(path: directoryManager.getDeviceSupportPath()) + directoriesCount += deviceSupportDirectories.count + + let archivesDirectories = directoryManager.getSubDirectoriesForPath(path: directoryManager.getArchivesPath()) + directoriesCount += archivesDirectories.count + + DispatchQueue.global(qos: .userInitiated).async { + self.calculateSize(ofDirectory: &self.derivedData, subDirectories: derivedDataDirectories, type: .derivedData) + self.calculateSize(ofDirectory: &self.deviceSupport, subDirectories: deviceSupportDirectories, type: .deviceSupport) + self.calculateSize(ofDirectory: &self.archives, subDirectories: archivesDirectories, type: .archives) + + self.isScanStarted.toggle() + self.isReadyToBeCleaned.toggle() + + DispatchQueue.main.async { + self.objectWillChange.send() + } + } + } + func calculateSize(ofDirectory: inout [DirectoryModel], subDirectories: [String], type: DirectoryType) { + ofDirectory = subDirectories.map { directory in + let normalizedDirectoryPathForDisplay = self.directoryManager.normalizeDirectoryPathForDisplay(directory: directory, forType: type) + let normalizedDirectoryPath = self.directoryManager.normalizeDirectoryPath(directory: directory) + + + let directorySize = self.directoryManager.getDirectorySize(path: normalizedDirectoryPath) { + self.analyzedDirectoriesCount += 1 + } + + DispatchQueue.main.async { + self.totalSize += directorySize + self.objectWillChange.send() + } + + return DirectoryModel(name: normalizedDirectoryPathForDisplay, size: directorySize) + } + } + func getViewModelForItemList(forType type: DirectoryType) -> DirectoryListViewModelProtocol { + var directoryName: String + var directories: [DirectoryModel] + var circleColor: Color + + switch type { + case .derivedData: + directories = derivedData + directoryName = "Derived Data" + circleColor = .pink + case .deviceSupport: + directories = deviceSupport + directoryName = "Device Support" + circleColor = Color(.cyan) + case .archives: + directories = archives + directoryName = "Archives" + circleColor = .orange + } + + var viewModel = DirectoryListViewModel(directoryName: directoryName, directories: directories, circleColor: circleColor) + viewModel.calculateTotalSize() + + return viewModel + } + func cleanBeforeScan() { + directoriesCount = 0 + analyzedDirectoriesCount = 0 + totalSize = 0 + + derivedData.removeAll() + deviceSupport.removeAll() + archives.removeAll() + + objectWillChange.send() + } + func getViewModelForPieChart() -> PieChartViewModelProtocol { + var viewModel = PieChartViewModel() + viewModel.createItems(derivedData: derivedData, deviceSupport: deviceSupport, archives: archives) + + return viewModel + } + //add chosen type + func startClean() { + DispatchQueue.global(qos: .userInitiated).async { + self.directoryManager.cleanDirectory(forType: .derivedData) + self.directoryManager.cleanDirectory(forType: .deviceSupport) + self.directoryManager.cleanDirectory(forType: .archives) + + DispatchQueue.main.async { + let statistic = StatisticModel(totalCleaned: self.totalSize, lastTimeCleaned: DateManager.getCurrentDate()) + + CoreDataManager.shared.saveStatistic(statistic: statistic) + + self.isAlertPresented.toggle() + self.objectWillChange.send() + + self.isReadyToBeCleaned.toggle() + } + } + } + func getViewModelForStatistic() -> StatisticViewModelProtocol { + let statistic = CoreDataManager.shared.getStatistic() + let viewModel = StatisticViewModel(statistic: statistic) + + return viewModel + } +} diff --git a/XcodeCleaner/Views/BodyViews/BodyView.swift b/XcodeCleaner/Views/BodyViews/BodyView.swift new file mode 100644 index 0000000..bab24e5 --- /dev/null +++ b/XcodeCleaner/Views/BodyViews/BodyView.swift @@ -0,0 +1,37 @@ +// +// BodyView.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 02.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct BodyView: View { + var viewModel: PieChartViewModelProtocol + + var body: some View { + HStack { + DirectoryListView() + Spacer() + GeometryReader { geometryReader in + VStack { + PieChartView(items: self.viewModel.getPCItems(), sliceSeparatorColor: .black) + .frame(width: geometryReader.size.width / 2, height: geometryReader.size.height / 2) + .animation(.easeIn(duration: 1.0)) + } + } + } + } +} + +struct MainView_Previews: PreviewProvider { + static var previews: some View { + var viewModel = PieChartViewModel() + viewModel.createItems(derivedData: [DirectoryModel(name: "Test", size: 2)], deviceSupport: [DirectoryModel(name: "Test", size: 2)], archives: [DirectoryModel(name: "Test", size: 2)]) + + return BodyView(viewModel: viewModel) + .environmentObject(ViewModel()) + } +} diff --git a/XcodeCleaner/Views/BodyViews/DirectoryListView.swift b/XcodeCleaner/Views/BodyViews/DirectoryListView.swift new file mode 100644 index 0000000..b800fdd --- /dev/null +++ b/XcodeCleaner/Views/BodyViews/DirectoryListView.swift @@ -0,0 +1,37 @@ +// +// DirectoryListView.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 24.06.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct DirectoryListView: View { + @EnvironmentObject var viewModel: ViewModel + var body: some View { + VStack(alignment: .leading) { + Spacer() + + DropDownView(viewModel: viewModel.getViewModelForItemList(forType: .derivedData)) + .padding() + Spacer() + + DropDownView(viewModel: viewModel.getViewModelForItemList(forType: .deviceSupport)) + .padding() + Spacer() + + DropDownView(viewModel: viewModel.getViewModelForItemList(forType: .archives)) + .padding() + Spacer() + } + } +} + +struct ItemsListView_Previews: PreviewProvider { + static var previews: some View { + DirectoryListView() + .environmentObject(ViewModel()) + } +} diff --git a/XcodeCleaner/Views/BodyViews/DropDownView.swift b/XcodeCleaner/Views/BodyViews/DropDownView.swift new file mode 100644 index 0000000..2cfb0db --- /dev/null +++ b/XcodeCleaner/Views/BodyViews/DropDownView.swift @@ -0,0 +1,67 @@ +// +// DropDownView.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 04.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct DropDownView: View { + @State var isExpanded = false + var viewModel: DirectoryListViewModelProtocol + + var body: some View { + VStack(spacing: 5) { + HStack { + HStack { + Text("\(viewModel.directories.count == 0 ? "": isExpanded ? "▲": "▼") \(viewModel.directoryName)") + .font(.largeTitle) + .fontWeight(.heavy) + + Text("\(BytesToStringFormatter.format(size: viewModel.totalSize))") + .foregroundColor(viewModel.circleColor) + .font(.largeTitle) + .fontWeight(.heavy) + + Circle() + .fill(viewModel.circleColor) + .frame(width: 30, height: 30) + } + }.onTapGesture { + self.isExpanded.toggle() + } + if viewModel.directories.count > 0 { + if isExpanded { + VStack { + GeometryReader { geometryReader in + ScrollView { + VStack { + ForEach(0 ..< self.viewModel.directories.count) { index in + HStack { + Text(self.viewModel.directories[index].name) + .lineLimit(1) + Spacer() + Text("\(BytesToStringFormatter.format(size: self.viewModel.directories[index].size))") + .foregroundColor(self.viewModel.circleColor) + } + } + } + .padding(.top, geometryReader.size.height / 2) + } + .font(.title) + } + } + } + } + } + .animation(.easeIn(duration: 0.3)) + } +} + +struct DropDownView_Previews: PreviewProvider { + static var previews: some View { + DropDownView(viewModel: DirectoryListViewModel(directoryName: "Test", directories: [DirectoryModel(name: "/Url", size: 15)], circleColor: .green)) + } +} diff --git a/XcodeCleaner/Views/ContentView.swift b/XcodeCleaner/Views/ContentView.swift new file mode 100644 index 0000000..a92fa53 --- /dev/null +++ b/XcodeCleaner/Views/ContentView.swift @@ -0,0 +1,32 @@ +// +// ContentView.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 07.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct ContentView: View { + @EnvironmentObject var viewModel: ViewModel + var body: some View { + VStack { + BodyView(viewModel: viewModel.getViewModelForPieChart()) + FooterView() + } + .alert(isPresented: $viewModel.isAlertPresented) { + Alert(title: Text("Clean finished"), message: Text("\(BytesToStringFormatter.format(size: viewModel.totalSize)) was successfully cleaned!"), dismissButton: .default( + Text("Ok"), action: { + self.viewModel.cleanBeforeScan() + }))} + .frame(minWidth: 800, minHeight: 550) + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + .environmentObject(ViewModel()) + } +} diff --git a/XcodeCleaner/Views/FooterViews/DividerButtonView.swift b/XcodeCleaner/Views/FooterViews/DividerButtonView.swift new file mode 100644 index 0000000..7817968 --- /dev/null +++ b/XcodeCleaner/Views/FooterViews/DividerButtonView.swift @@ -0,0 +1,32 @@ +// +// DividerButtonView.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 03.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct DividerButtonView: View { + var divider = VStack { Divider() } + @EnvironmentObject var viewModel: ViewModel + + var body: some View { + HStack { + divider + Button("\(viewModel.isReadyToBeCleaned ? "Clean": "Scan")") { + self.viewModel.isReadyToBeCleaned ? self.viewModel.startClean(): self.viewModel.startScan() + } + .cornerRadius(25) + divider + } + } +} + +struct DividerButtonView_Previews: PreviewProvider { + static var previews: some View { + DividerButtonView() + .environmentObject(ViewModel()) + } +} diff --git a/XcodeCleaner/Views/FooterViews/FooterView.swift b/XcodeCleaner/Views/FooterViews/FooterView.swift new file mode 100644 index 0000000..4663a88 --- /dev/null +++ b/XcodeCleaner/Views/FooterViews/FooterView.swift @@ -0,0 +1,42 @@ +// +// FooterView.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 02.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct FooterView: View { + @EnvironmentObject var viewModel: ViewModel + + var body: some View { + return VStack { + DividerButtonView() + .offset(y: 16) + + HStack { + ScanProgressView(progress: CGFloat(viewModel.scanProgress)) + .padding(10) + + Text("\(BytesToStringFormatter.format(size: viewModel.totalSize, allowedUnits: [.useGB]))") + .font(.title) + .fontWeight(.heavy) + .foregroundColor(.pink) + .frame(width: 100) + .lineLimit(1) + + StatisticView(viewModel: viewModel.getViewModelForStatistic()) + .padding(10) + } + } + } +} + +struct FooterView_Previews: PreviewProvider { + static var previews: some View { + FooterView() + .environmentObject(ViewModel()) + } +} diff --git a/XcodeCleaner/Views/FooterViews/ScanProgressView.swift b/XcodeCleaner/Views/FooterViews/ScanProgressView.swift new file mode 100644 index 0000000..1bcae2c --- /dev/null +++ b/XcodeCleaner/Views/FooterViews/ScanProgressView.swift @@ -0,0 +1,50 @@ +// +// ScanProgressView.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 03.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct ScanProgressView: View { + var progress: CGFloat = 0.0 + var height: CGFloat = 8 + var startPoint = CGPoint(x: 0, y: 0) + + var body: some View { + GeometryReader { geometryReader in + ZStack { + Path { path in + path.move(to: self.startPoint) + path.addRoundedRect( + in: CGRect(x: self.startPoint.x, y: self.startPoint.y, width: geometryReader.size.width, height: self.height), + cornerSize: CGSize(width: self.height / 2 , height: self.height / 2), + style: .circular) + } + + Path { path in + path.move(to: self.startPoint) + path.addRoundedRect( + in: CGRect(x: self.startPoint.x, y: self.startPoint.y, width: geometryReader.size.width * self.progress, height: self.height), + cornerSize: CGSize(width: self.height / 2, height: self.height / 2), + style: .circular) + } + .fill(LinearGradient(gradient: Gradient(colors: [Color(.cyan), .pink, .orange]), startPoint: .leading, endPoint: .trailing)) + .animation(.easeInOut) + } + .frame(width: geometryReader.size.width, height: self.height) + } + .frame(height: self.height) + } +} + +struct ScanProgressView_Previews: PreviewProvider { + static var previews: some View { + VStack { + ScanProgressView() + ScanProgressView(progress: 1.0) + } + } +} diff --git a/XcodeCleaner/Views/FooterViews/StatisticView.swift b/XcodeCleaner/Views/FooterViews/StatisticView.swift new file mode 100644 index 0000000..c8123b9 --- /dev/null +++ b/XcodeCleaner/Views/FooterViews/StatisticView.swift @@ -0,0 +1,27 @@ +// +// StatisticView.swift +// XcodeCleaner +// +// Created by Kirill Pustovalov on 02.07.2020. +// Copyright © 2020 Kirill Pustovalov. All rights reserved. +// + +import SwiftUI + +struct StatisticView: View { + var viewModel: StatisticViewModelProtocol + var body: some View { + VStack(alignment: .trailing) { + Text("Total cleaned: \(viewModel.getTotalSize())") + Text("Last cleanup: \(viewModel.getLastDate())") + Text("Github: IrelDev") + } + .font(.footnote) + } +} + +struct StatisticView_Previews: PreviewProvider { + static var previews: some View { + StatisticView(viewModel: StatisticViewModel(statistic: StatisticModel(totalCleaned: 250000, lastTimeCleaned: Date()))) + } +} diff --git a/XcodeCleaner/XcodeCleaner.entitlements b/XcodeCleaner/XcodeCleaner.entitlements new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/XcodeCleaner/XcodeCleaner.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/XcodeCleaner/XcodeCleaner.xcdatamodeld/.xccurrentversion b/XcodeCleaner/XcodeCleaner.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..6384fd4 --- /dev/null +++ b/XcodeCleaner/XcodeCleaner.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + XcodeCleaner.xcdatamodel + + diff --git a/XcodeCleaner/XcodeCleaner.xcdatamodeld/XcodeCleaner.xcdatamodel/contents b/XcodeCleaner/XcodeCleaner.xcdatamodeld/XcodeCleaner.xcdatamodel/contents new file mode 100644 index 0000000..daf8bb4 --- /dev/null +++ b/XcodeCleaner/XcodeCleaner.xcdatamodeld/XcodeCleaner.xcdatamodel/contents @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file