├── .gitignore
├── Assets
├── Demo.gif
└── Logo.png
├── LICENSE
├── README.md
├── XcodeCleaner.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcuserdata
│ └── revilarva.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
└── XcodeCleaner
├── AppDelegate.swift
├── Assets.xcassets
├── AppIcon.appiconset
│ ├── Contents.json
│ ├── mac-128x128.png
│ ├── mac-128x128@2x.png
│ ├── mac-16x16.png
│ ├── mac-16x16@2x.png
│ ├── mac-256x256.png
│ ├── mac-256x256@2x.png
│ ├── mac-32x32.png
│ ├── mac-32x32@2x.png
│ ├── mac-512x512.png
│ └── mac-512x512@2x.png
└── Contents.json
├── Base.lproj
└── Main.storyboard
├── CoreDataManager.swift
├── DirectoryManager.swift
├── Enums
├── DirectoryType.swift
└── FileType.swift
├── Helpers
├── BytesToStringFormatter.swift
└── DateManager.swift
├── Info.plist
├── Models
├── DirectoryModel.swift
└── StatisticModel.swift
├── PieChart
├── Models
│ ├── PieChartItemModel.swift
│ ├── PieChartObservableItemsModel.swift
│ └── PieChartSliceModel.swift
├── PieChartSliceFactory.swift
├── Shapes
│ └── PieChartSliceShape.swift
└── Views
│ ├── PieChartSliceView.swift
│ └── PieChartView.swift
├── Preview Content
└── Preview Assets.xcassets
│ └── Contents.json
├── Protocols
├── DirectoryListViewModelProtocol.swift
├── PieChartViewModelProtocol.swift
├── StatisticViewModelProtocol.swift
└── ViewModelProtocol.swift
├── ViewModels
├── DirectoryListViewModel.swift
├── ObservableFilterModel.swift
├── PieChartViewModel.swift
├── StatisticViewModel.swift
└── ViewModel.swift
├── Views
├── ActivityIndicatorView.swift
├── BodyViews
│ ├── BodyView.swift
│ ├── DirectoryListView.swift
│ └── DropDownView.swift
├── ContentView.swift
└── FooterViews
│ ├── DividerButtonView.swift
│ ├── FooterView.swift
│ ├── ScanProgressView.swift
│ └── StatisticView.swift
├── XcodeCleaner.entitlements
└── XcodeCleaner.xcdatamodeld
├── .xccurrentversion
└── XcodeCleaner.xcdatamodel
└── contents
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/Assets/Demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/Assets/Demo.gif
--------------------------------------------------------------------------------
/Assets/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/Assets/Logo.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Kirill Pustovalov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 |
5 |
6 |
7 |
8 | ## Installation
9 | 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.
10 |
11 | ## Demo
12 |
13 | 
14 |
15 |
16 | ## License
17 | XcodeCleaner is available under the MIT license, see the [LICENSE](LICENSE) file for more information.
18 |
--------------------------------------------------------------------------------
/XcodeCleaner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 437E9B7E254AD908009DA4DC /* ObservableFilterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437E9B7D254AD908009DA4DC /* ObservableFilterModel.swift */; };
11 | 9D5A202724BC1A7F0066303A /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D5A202624BC1A7F0066303A /* ActivityIndicatorView.swift */; };
12 | 9DB4448224BAEFEE00165FB9 /* FileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DB4448124BAEFEE00165FB9 /* FileType.swift */; };
13 | 9DCE1C0B24B582D200E49E6D /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCE1C0A24B582D200E49E6D /* CoreDataManager.swift */; };
14 | 9DD3BA7024B4639400EDA308 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA6F24B4639400EDA308 /* AppDelegate.swift */; };
15 | 9DD3BA7224B4639400EDA308 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7124B4639400EDA308 /* ContentView.swift */; };
16 | 9DD3BA7524B4639400EDA308 /* XcodeCleaner.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7324B4639400EDA308 /* XcodeCleaner.xcdatamodeld */; };
17 | 9DD3BA7724B4639700EDA308 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7624B4639700EDA308 /* Assets.xcassets */; };
18 | 9DD3BA7A24B4639700EDA308 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7924B4639700EDA308 /* Preview Assets.xcassets */; };
19 | 9DD3BA7D24B4639700EDA308 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA7B24B4639700EDA308 /* Main.storyboard */; };
20 | 9DD3BA8B24B465B800EDA308 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA8924B465B800EDA308 /* README.md */; };
21 | 9DD3BA8C24B465B800EDA308 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 9DD3BA8A24B465B800EDA308 /* LICENSE */; };
22 | 9DD3BA8E24B478AE00EDA308 /* DirectoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA8D24B478AE00EDA308 /* DirectoryManager.swift */; };
23 | 9DD3BA9024B478B600EDA308 /* DirectoryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA8F24B478B600EDA308 /* DirectoryType.swift */; };
24 | 9DD3BA9424B478C900EDA308 /* DirectoryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9224B478C900EDA308 /* DirectoryModel.swift */; };
25 | 9DD3BA9524B478C900EDA308 /* StatisticModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9324B478C900EDA308 /* StatisticModel.swift */; };
26 | 9DD3BA9E24B4791900EDA308 /* DropDownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9B24B4791900EDA308 /* DropDownView.swift */; };
27 | 9DD3BA9F24B4791900EDA308 /* BodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9C24B4791900EDA308 /* BodyView.swift */; };
28 | 9DD3BAA024B4791900EDA308 /* DirectoryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BA9D24B4791900EDA308 /* DirectoryListView.swift */; };
29 | 9DD3BAA224B4793900EDA308 /* FooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAA124B4793900EDA308 /* FooterView.swift */; };
30 | 9DD3BAA424B4794300EDA308 /* DividerButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAA324B4794300EDA308 /* DividerButtonView.swift */; };
31 | 9DD3BAA624B4794800EDA308 /* StatisticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAA524B4794800EDA308 /* StatisticView.swift */; };
32 | 9DD3BAA824B4794C00EDA308 /* ScanProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAA724B4794C00EDA308 /* ScanProgressView.swift */; };
33 | 9DD3BAAE24B4796C00EDA308 /* StatisticViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAAA24B4796C00EDA308 /* StatisticViewModel.swift */; };
34 | 9DD3BAAF24B4796C00EDA308 /* PieChartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAAB24B4796C00EDA308 /* PieChartViewModel.swift */; };
35 | 9DD3BAB024B4796C00EDA308 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAAC24B4796C00EDA308 /* ViewModel.swift */; };
36 | 9DD3BAB124B4796C00EDA308 /* DirectoryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAAD24B4796C00EDA308 /* DirectoryListViewModel.swift */; };
37 | 9DD3BAB724B4798E00EDA308 /* DirectoryListViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAB324B4798E00EDA308 /* DirectoryListViewModelProtocol.swift */; };
38 | 9DD3BAB824B4798E00EDA308 /* PieChartViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAB424B4798E00EDA308 /* PieChartViewModelProtocol.swift */; };
39 | 9DD3BAB924B4798E00EDA308 /* ViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAB524B4798E00EDA308 /* ViewModelProtocol.swift */; };
40 | 9DD3BABA24B4798E00EDA308 /* StatisticViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAB624B4798E00EDA308 /* StatisticViewModelProtocol.swift */; };
41 | 9DD3BABD24B479BF00EDA308 /* BytesToStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BABC24B479BF00EDA308 /* BytesToStringFormatter.swift */; };
42 | 9DD3BAC024B479DF00EDA308 /* PieChartSliceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BABF24B479DF00EDA308 /* PieChartSliceFactory.swift */; };
43 | 9DD3BAC624B479ED00EDA308 /* PieChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAC324B479ED00EDA308 /* PieChartView.swift */; };
44 | 9DD3BAC724B479ED00EDA308 /* PieChartSliceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAC424B479ED00EDA308 /* PieChartSliceView.swift */; };
45 | 9DD3BACC24B47A0400EDA308 /* PieChartSliceShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BACA24B47A0400EDA308 /* PieChartSliceShape.swift */; };
46 | 9DD3BAD224B47A1E00EDA308 /* PieChartSliceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BACE24B47A1D00EDA308 /* PieChartSliceModel.swift */; };
47 | 9DD3BAD324B47A1E00EDA308 /* PieChartObservableItemsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BACF24B47A1E00EDA308 /* PieChartObservableItemsModel.swift */; };
48 | 9DD3BAD424B47A1E00EDA308 /* PieChartItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD3BAD024B47A1E00EDA308 /* PieChartItemModel.swift */; };
49 | 9DDFA81D24B58F47008C2C2A /* DateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DDFA81C24B58F47008C2C2A /* DateManager.swift */; };
50 | /* End PBXBuildFile section */
51 |
52 | /* Begin PBXFileReference section */
53 | 437E9B7D254AD908009DA4DC /* ObservableFilterModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableFilterModel.swift; sourceTree = ""; };
54 | 9D5A202624BC1A7F0066303A /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = ""; };
55 | 9DB4448124BAEFEE00165FB9 /* FileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileType.swift; sourceTree = ""; };
56 | 9DCE1C0A24B582D200E49E6D /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; };
57 | 9DD3BA6C24B4639400EDA308 /* XcodeCleaner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XcodeCleaner.app; sourceTree = BUILT_PRODUCTS_DIR; };
58 | 9DD3BA6F24B4639400EDA308 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
59 | 9DD3BA7124B4639400EDA308 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
60 | 9DD3BA7424B4639400EDA308 /* XcodeCleaner.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = XcodeCleaner.xcdatamodel; sourceTree = ""; };
61 | 9DD3BA7624B4639700EDA308 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
62 | 9DD3BA7924B4639700EDA308 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
63 | 9DD3BA7C24B4639700EDA308 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
64 | 9DD3BA7E24B4639700EDA308 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
65 | 9DD3BA7F24B4639700EDA308 /* XcodeCleaner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = XcodeCleaner.entitlements; sourceTree = ""; };
66 | 9DD3BA8924B465B800EDA308 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
67 | 9DD3BA8A24B465B800EDA308 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
68 | 9DD3BA8D24B478AE00EDA308 /* DirectoryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryManager.swift; sourceTree = ""; };
69 | 9DD3BA8F24B478B600EDA308 /* DirectoryType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryType.swift; sourceTree = ""; };
70 | 9DD3BA9224B478C900EDA308 /* DirectoryModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryModel.swift; sourceTree = ""; };
71 | 9DD3BA9324B478C900EDA308 /* StatisticModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticModel.swift; sourceTree = ""; };
72 | 9DD3BA9B24B4791900EDA308 /* DropDownView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownView.swift; sourceTree = ""; };
73 | 9DD3BA9C24B4791900EDA308 /* BodyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BodyView.swift; sourceTree = ""; };
74 | 9DD3BA9D24B4791900EDA308 /* DirectoryListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryListView.swift; sourceTree = ""; };
75 | 9DD3BAA124B4793900EDA308 /* FooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; };
76 | 9DD3BAA324B4794300EDA308 /* DividerButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DividerButtonView.swift; sourceTree = ""; };
77 | 9DD3BAA524B4794800EDA308 /* StatisticView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticView.swift; sourceTree = ""; };
78 | 9DD3BAA724B4794C00EDA308 /* ScanProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanProgressView.swift; sourceTree = ""; };
79 | 9DD3BAAA24B4796C00EDA308 /* StatisticViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticViewModel.swift; sourceTree = ""; };
80 | 9DD3BAAB24B4796C00EDA308 /* PieChartViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartViewModel.swift; sourceTree = ""; };
81 | 9DD3BAAC24B4796C00EDA308 /* ViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; };
82 | 9DD3BAAD24B4796C00EDA308 /* DirectoryListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryListViewModel.swift; sourceTree = ""; };
83 | 9DD3BAB324B4798E00EDA308 /* DirectoryListViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryListViewModelProtocol.swift; sourceTree = ""; };
84 | 9DD3BAB424B4798E00EDA308 /* PieChartViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartViewModelProtocol.swift; sourceTree = ""; };
85 | 9DD3BAB524B4798E00EDA308 /* ViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModelProtocol.swift; sourceTree = ""; };
86 | 9DD3BAB624B4798E00EDA308 /* StatisticViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticViewModelProtocol.swift; sourceTree = ""; };
87 | 9DD3BABC24B479BF00EDA308 /* BytesToStringFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BytesToStringFormatter.swift; sourceTree = ""; };
88 | 9DD3BABF24B479DF00EDA308 /* PieChartSliceFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSliceFactory.swift; sourceTree = ""; };
89 | 9DD3BAC324B479ED00EDA308 /* PieChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartView.swift; sourceTree = ""; };
90 | 9DD3BAC424B479ED00EDA308 /* PieChartSliceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSliceView.swift; sourceTree = ""; };
91 | 9DD3BACA24B47A0400EDA308 /* PieChartSliceShape.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSliceShape.swift; sourceTree = ""; };
92 | 9DD3BACE24B47A1D00EDA308 /* PieChartSliceModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartSliceModel.swift; sourceTree = ""; };
93 | 9DD3BACF24B47A1E00EDA308 /* PieChartObservableItemsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartObservableItemsModel.swift; sourceTree = ""; };
94 | 9DD3BAD024B47A1E00EDA308 /* PieChartItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartItemModel.swift; sourceTree = ""; };
95 | 9DDFA81C24B58F47008C2C2A /* DateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateManager.swift; sourceTree = ""; };
96 | /* End PBXFileReference section */
97 |
98 | /* Begin PBXFrameworksBuildPhase section */
99 | 9DD3BA6924B4639400EDA308 /* Frameworks */ = {
100 | isa = PBXFrameworksBuildPhase;
101 | buildActionMask = 2147483647;
102 | files = (
103 | );
104 | runOnlyForDeploymentPostprocessing = 0;
105 | };
106 | /* End PBXFrameworksBuildPhase section */
107 |
108 | /* Begin PBXGroup section */
109 | 9DB4448024BAEFCE00165FB9 /* Enums */ = {
110 | isa = PBXGroup;
111 | children = (
112 | 9DD3BA8F24B478B600EDA308 /* DirectoryType.swift */,
113 | 9DB4448124BAEFEE00165FB9 /* FileType.swift */,
114 | );
115 | path = Enums;
116 | sourceTree = "";
117 | };
118 | 9DD3BA6324B4639400EDA308 = {
119 | isa = PBXGroup;
120 | children = (
121 | 9DD3BA8A24B465B800EDA308 /* LICENSE */,
122 | 9DD3BA8924B465B800EDA308 /* README.md */,
123 | 9DD3BA6E24B4639400EDA308 /* XcodeCleaner */,
124 | 9DD3BA6D24B4639400EDA308 /* Products */,
125 | );
126 | sourceTree = "";
127 | };
128 | 9DD3BA6D24B4639400EDA308 /* Products */ = {
129 | isa = PBXGroup;
130 | children = (
131 | 9DD3BA6C24B4639400EDA308 /* XcodeCleaner.app */,
132 | );
133 | name = Products;
134 | sourceTree = "";
135 | };
136 | 9DD3BA6E24B4639400EDA308 /* XcodeCleaner */ = {
137 | isa = PBXGroup;
138 | children = (
139 | 9DD3BA6F24B4639400EDA308 /* AppDelegate.swift */,
140 | 9DD3BA8D24B478AE00EDA308 /* DirectoryManager.swift */,
141 | 9DCE1C0A24B582D200E49E6D /* CoreDataManager.swift */,
142 | 9DD3BA9124B478C100EDA308 /* Models */,
143 | 9DD3BA9624B478CC00EDA308 /* Views */,
144 | 9DD3BAA924B4795A00EDA308 /* ViewModels */,
145 | 9DD3BAB224B4798200EDA308 /* Protocols */,
146 | 9DD3BABB24B479B300EDA308 /* Helpers */,
147 | 9DB4448024BAEFCE00165FB9 /* Enums */,
148 | 9DD3BABE24B479C900EDA308 /* PieChart */,
149 | 9DD3BA7624B4639700EDA308 /* Assets.xcassets */,
150 | 9DD3BA7B24B4639700EDA308 /* Main.storyboard */,
151 | 9DD3BA7E24B4639700EDA308 /* Info.plist */,
152 | 9DD3BA7F24B4639700EDA308 /* XcodeCleaner.entitlements */,
153 | 9DD3BA7324B4639400EDA308 /* XcodeCleaner.xcdatamodeld */,
154 | 9DD3BA7824B4639700EDA308 /* Preview Content */,
155 | );
156 | path = XcodeCleaner;
157 | sourceTree = "";
158 | };
159 | 9DD3BA7824B4639700EDA308 /* Preview Content */ = {
160 | isa = PBXGroup;
161 | children = (
162 | 9DD3BA7924B4639700EDA308 /* Preview Assets.xcassets */,
163 | );
164 | path = "Preview Content";
165 | sourceTree = "";
166 | };
167 | 9DD3BA9124B478C100EDA308 /* Models */ = {
168 | isa = PBXGroup;
169 | children = (
170 | 9DD3BA9224B478C900EDA308 /* DirectoryModel.swift */,
171 | 9DD3BA9324B478C900EDA308 /* StatisticModel.swift */,
172 | );
173 | path = Models;
174 | sourceTree = "";
175 | };
176 | 9DD3BA9624B478CC00EDA308 /* Views */ = {
177 | isa = PBXGroup;
178 | children = (
179 | 9DD3BA7124B4639400EDA308 /* ContentView.swift */,
180 | 9D5A202624BC1A7F0066303A /* ActivityIndicatorView.swift */,
181 | 9DD3BA9924B478FD00EDA308 /* BodyViews */,
182 | 9DD3BA9A24B4790200EDA308 /* FooterViews */,
183 | );
184 | path = Views;
185 | sourceTree = "";
186 | };
187 | 9DD3BA9924B478FD00EDA308 /* BodyViews */ = {
188 | isa = PBXGroup;
189 | children = (
190 | 9DD3BA9C24B4791900EDA308 /* BodyView.swift */,
191 | 9DD3BA9D24B4791900EDA308 /* DirectoryListView.swift */,
192 | 9DD3BA9B24B4791900EDA308 /* DropDownView.swift */,
193 | );
194 | path = BodyViews;
195 | sourceTree = "";
196 | };
197 | 9DD3BA9A24B4790200EDA308 /* FooterViews */ = {
198 | isa = PBXGroup;
199 | children = (
200 | 9DD3BAA124B4793900EDA308 /* FooterView.swift */,
201 | 9DD3BAA324B4794300EDA308 /* DividerButtonView.swift */,
202 | 9DD3BAA524B4794800EDA308 /* StatisticView.swift */,
203 | 9DD3BAA724B4794C00EDA308 /* ScanProgressView.swift */,
204 | );
205 | path = FooterViews;
206 | sourceTree = "";
207 | };
208 | 9DD3BAA924B4795A00EDA308 /* ViewModels */ = {
209 | isa = PBXGroup;
210 | children = (
211 | 9DD3BAAC24B4796C00EDA308 /* ViewModel.swift */,
212 | 9DD3BAAD24B4796C00EDA308 /* DirectoryListViewModel.swift */,
213 | 9DD3BAAB24B4796C00EDA308 /* PieChartViewModel.swift */,
214 | 9DD3BAAA24B4796C00EDA308 /* StatisticViewModel.swift */,
215 | 437E9B7D254AD908009DA4DC /* ObservableFilterModel.swift */,
216 | );
217 | path = ViewModels;
218 | sourceTree = "";
219 | };
220 | 9DD3BAB224B4798200EDA308 /* Protocols */ = {
221 | isa = PBXGroup;
222 | children = (
223 | 9DD3BAB524B4798E00EDA308 /* ViewModelProtocol.swift */,
224 | 9DD3BAB424B4798E00EDA308 /* PieChartViewModelProtocol.swift */,
225 | 9DD3BAB324B4798E00EDA308 /* DirectoryListViewModelProtocol.swift */,
226 | 9DD3BAB624B4798E00EDA308 /* StatisticViewModelProtocol.swift */,
227 | );
228 | path = Protocols;
229 | sourceTree = "";
230 | };
231 | 9DD3BABB24B479B300EDA308 /* Helpers */ = {
232 | isa = PBXGroup;
233 | children = (
234 | 9DD3BABC24B479BF00EDA308 /* BytesToStringFormatter.swift */,
235 | 9DDFA81C24B58F47008C2C2A /* DateManager.swift */,
236 | );
237 | path = Helpers;
238 | sourceTree = "";
239 | };
240 | 9DD3BABE24B479C900EDA308 /* PieChart */ = {
241 | isa = PBXGroup;
242 | children = (
243 | 9DD3BABF24B479DF00EDA308 /* PieChartSliceFactory.swift */,
244 | 9DD3BAC124B479E500EDA308 /* Views */,
245 | 9DD3BAC824B479F300EDA308 /* Shapes */,
246 | 9DD3BACD24B47A1000EDA308 /* Models */,
247 | );
248 | path = PieChart;
249 | sourceTree = "";
250 | };
251 | 9DD3BAC124B479E500EDA308 /* Views */ = {
252 | isa = PBXGroup;
253 | children = (
254 | 9DD3BAC424B479ED00EDA308 /* PieChartSliceView.swift */,
255 | 9DD3BAC324B479ED00EDA308 /* PieChartView.swift */,
256 | );
257 | path = Views;
258 | sourceTree = "";
259 | };
260 | 9DD3BAC824B479F300EDA308 /* Shapes */ = {
261 | isa = PBXGroup;
262 | children = (
263 | 9DD3BACA24B47A0400EDA308 /* PieChartSliceShape.swift */,
264 | );
265 | path = Shapes;
266 | sourceTree = "";
267 | };
268 | 9DD3BACD24B47A1000EDA308 /* Models */ = {
269 | isa = PBXGroup;
270 | children = (
271 | 9DD3BAD024B47A1E00EDA308 /* PieChartItemModel.swift */,
272 | 9DD3BACF24B47A1E00EDA308 /* PieChartObservableItemsModel.swift */,
273 | 9DD3BACE24B47A1D00EDA308 /* PieChartSliceModel.swift */,
274 | );
275 | path = Models;
276 | sourceTree = "";
277 | };
278 | /* End PBXGroup section */
279 |
280 | /* Begin PBXNativeTarget section */
281 | 9DD3BA6B24B4639400EDA308 /* XcodeCleaner */ = {
282 | isa = PBXNativeTarget;
283 | buildConfigurationList = 9DD3BA8224B4639700EDA308 /* Build configuration list for PBXNativeTarget "XcodeCleaner" */;
284 | buildPhases = (
285 | 9DD3BA6824B4639400EDA308 /* Sources */,
286 | 9DD3BA6924B4639400EDA308 /* Frameworks */,
287 | 9DD3BA6A24B4639400EDA308 /* Resources */,
288 | );
289 | buildRules = (
290 | );
291 | dependencies = (
292 | );
293 | name = XcodeCleaner;
294 | productName = XcodeCleaner;
295 | productReference = 9DD3BA6C24B4639400EDA308 /* XcodeCleaner.app */;
296 | productType = "com.apple.product-type.application";
297 | };
298 | /* End PBXNativeTarget section */
299 |
300 | /* Begin PBXProject section */
301 | 9DD3BA6424B4639400EDA308 /* Project object */ = {
302 | isa = PBXProject;
303 | attributes = {
304 | LastSwiftUpdateCheck = 1150;
305 | LastUpgradeCheck = 1200;
306 | ORGANIZATIONNAME = "Kirill Pustovalov";
307 | TargetAttributes = {
308 | 9DD3BA6B24B4639400EDA308 = {
309 | CreatedOnToolsVersion = 11.5;
310 | };
311 | };
312 | };
313 | buildConfigurationList = 9DD3BA6724B4639400EDA308 /* Build configuration list for PBXProject "XcodeCleaner" */;
314 | compatibilityVersion = "Xcode 9.3";
315 | developmentRegion = en;
316 | hasScannedForEncodings = 0;
317 | knownRegions = (
318 | en,
319 | Base,
320 | );
321 | mainGroup = 9DD3BA6324B4639400EDA308;
322 | productRefGroup = 9DD3BA6D24B4639400EDA308 /* Products */;
323 | projectDirPath = "";
324 | projectRoot = "";
325 | targets = (
326 | 9DD3BA6B24B4639400EDA308 /* XcodeCleaner */,
327 | );
328 | };
329 | /* End PBXProject section */
330 |
331 | /* Begin PBXResourcesBuildPhase section */
332 | 9DD3BA6A24B4639400EDA308 /* Resources */ = {
333 | isa = PBXResourcesBuildPhase;
334 | buildActionMask = 2147483647;
335 | files = (
336 | 9DD3BA7D24B4639700EDA308 /* Main.storyboard in Resources */,
337 | 9DD3BA7A24B4639700EDA308 /* Preview Assets.xcassets in Resources */,
338 | 9DD3BA8B24B465B800EDA308 /* README.md in Resources */,
339 | 9DD3BA7724B4639700EDA308 /* Assets.xcassets in Resources */,
340 | 9DD3BA8C24B465B800EDA308 /* LICENSE in Resources */,
341 | );
342 | runOnlyForDeploymentPostprocessing = 0;
343 | };
344 | /* End PBXResourcesBuildPhase section */
345 |
346 | /* Begin PBXSourcesBuildPhase section */
347 | 9DD3BA6824B4639400EDA308 /* Sources */ = {
348 | isa = PBXSourcesBuildPhase;
349 | buildActionMask = 2147483647;
350 | files = (
351 | 9DD3BA7224B4639400EDA308 /* ContentView.swift in Sources */,
352 | 9DD3BA9524B478C900EDA308 /* StatisticModel.swift in Sources */,
353 | 9DD3BAD424B47A1E00EDA308 /* PieChartItemModel.swift in Sources */,
354 | 9DD3BAC624B479ED00EDA308 /* PieChartView.swift in Sources */,
355 | 9DD3BA9F24B4791900EDA308 /* BodyView.swift in Sources */,
356 | 9DD3BA9024B478B600EDA308 /* DirectoryType.swift in Sources */,
357 | 9DD3BACC24B47A0400EDA308 /* PieChartSliceShape.swift in Sources */,
358 | 9DD3BAA824B4794C00EDA308 /* ScanProgressView.swift in Sources */,
359 | 9DD3BA8E24B478AE00EDA308 /* DirectoryManager.swift in Sources */,
360 | 9DB4448224BAEFEE00165FB9 /* FileType.swift in Sources */,
361 | 9DD3BAB024B4796C00EDA308 /* ViewModel.swift in Sources */,
362 | 9DD3BA9424B478C900EDA308 /* DirectoryModel.swift in Sources */,
363 | 9DD3BAA224B4793900EDA308 /* FooterView.swift in Sources */,
364 | 9DD3BAC724B479ED00EDA308 /* PieChartSliceView.swift in Sources */,
365 | 9DD3BA9E24B4791900EDA308 /* DropDownView.swift in Sources */,
366 | 9DD3BABD24B479BF00EDA308 /* BytesToStringFormatter.swift in Sources */,
367 | 9DD3BABA24B4798E00EDA308 /* StatisticViewModelProtocol.swift in Sources */,
368 | 437E9B7E254AD908009DA4DC /* ObservableFilterModel.swift in Sources */,
369 | 9DD3BAAE24B4796C00EDA308 /* StatisticViewModel.swift in Sources */,
370 | 9DD3BAB924B4798E00EDA308 /* ViewModelProtocol.swift in Sources */,
371 | 9DD3BAA624B4794800EDA308 /* StatisticView.swift in Sources */,
372 | 9DD3BAA424B4794300EDA308 /* DividerButtonView.swift in Sources */,
373 | 9DD3BA7024B4639400EDA308 /* AppDelegate.swift in Sources */,
374 | 9DD3BAA024B4791900EDA308 /* DirectoryListView.swift in Sources */,
375 | 9DDFA81D24B58F47008C2C2A /* DateManager.swift in Sources */,
376 | 9DD3BAAF24B4796C00EDA308 /* PieChartViewModel.swift in Sources */,
377 | 9DD3BAD224B47A1E00EDA308 /* PieChartSliceModel.swift in Sources */,
378 | 9DD3BAB824B4798E00EDA308 /* PieChartViewModelProtocol.swift in Sources */,
379 | 9DD3BAB124B4796C00EDA308 /* DirectoryListViewModel.swift in Sources */,
380 | 9D5A202724BC1A7F0066303A /* ActivityIndicatorView.swift in Sources */,
381 | 9DD3BAC024B479DF00EDA308 /* PieChartSliceFactory.swift in Sources */,
382 | 9DD3BAD324B47A1E00EDA308 /* PieChartObservableItemsModel.swift in Sources */,
383 | 9DD3BA7524B4639400EDA308 /* XcodeCleaner.xcdatamodeld in Sources */,
384 | 9DD3BAB724B4798E00EDA308 /* DirectoryListViewModelProtocol.swift in Sources */,
385 | 9DCE1C0B24B582D200E49E6D /* CoreDataManager.swift in Sources */,
386 | );
387 | runOnlyForDeploymentPostprocessing = 0;
388 | };
389 | /* End PBXSourcesBuildPhase section */
390 |
391 | /* Begin PBXVariantGroup section */
392 | 9DD3BA7B24B4639700EDA308 /* Main.storyboard */ = {
393 | isa = PBXVariantGroup;
394 | children = (
395 | 9DD3BA7C24B4639700EDA308 /* Base */,
396 | );
397 | name = Main.storyboard;
398 | sourceTree = "";
399 | };
400 | /* End PBXVariantGroup section */
401 |
402 | /* Begin XCBuildConfiguration section */
403 | 9DD3BA8024B4639700EDA308 /* Debug */ = {
404 | isa = XCBuildConfiguration;
405 | buildSettings = {
406 | ALWAYS_SEARCH_USER_PATHS = NO;
407 | CLANG_ANALYZER_NONNULL = YES;
408 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
410 | CLANG_CXX_LIBRARY = "libc++";
411 | CLANG_ENABLE_MODULES = YES;
412 | CLANG_ENABLE_OBJC_ARC = YES;
413 | CLANG_ENABLE_OBJC_WEAK = YES;
414 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
415 | CLANG_WARN_BOOL_CONVERSION = YES;
416 | CLANG_WARN_COMMA = YES;
417 | CLANG_WARN_CONSTANT_CONVERSION = YES;
418 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
419 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
420 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
421 | CLANG_WARN_EMPTY_BODY = YES;
422 | CLANG_WARN_ENUM_CONVERSION = YES;
423 | CLANG_WARN_INFINITE_RECURSION = YES;
424 | CLANG_WARN_INT_CONVERSION = YES;
425 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
426 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
427 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
428 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
429 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
430 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
431 | CLANG_WARN_STRICT_PROTOTYPES = YES;
432 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
433 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
434 | CLANG_WARN_UNREACHABLE_CODE = YES;
435 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
436 | COPY_PHASE_STRIP = NO;
437 | DEBUG_INFORMATION_FORMAT = dwarf;
438 | ENABLE_STRICT_OBJC_MSGSEND = YES;
439 | ENABLE_TESTABILITY = YES;
440 | GCC_C_LANGUAGE_STANDARD = gnu11;
441 | GCC_DYNAMIC_NO_PIC = NO;
442 | GCC_NO_COMMON_BLOCKS = YES;
443 | GCC_OPTIMIZATION_LEVEL = 0;
444 | GCC_PREPROCESSOR_DEFINITIONS = (
445 | "DEBUG=1",
446 | "$(inherited)",
447 | );
448 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
449 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
450 | GCC_WARN_UNDECLARED_SELECTOR = YES;
451 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
452 | GCC_WARN_UNUSED_FUNCTION = YES;
453 | GCC_WARN_UNUSED_VARIABLE = YES;
454 | MACOSX_DEPLOYMENT_TARGET = 10.15;
455 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
456 | MTL_FAST_MATH = YES;
457 | ONLY_ACTIVE_ARCH = YES;
458 | SDKROOT = macosx;
459 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
460 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
461 | };
462 | name = Debug;
463 | };
464 | 9DD3BA8124B4639700EDA308 /* Release */ = {
465 | isa = XCBuildConfiguration;
466 | buildSettings = {
467 | ALWAYS_SEARCH_USER_PATHS = NO;
468 | CLANG_ANALYZER_NONNULL = YES;
469 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
470 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
471 | CLANG_CXX_LIBRARY = "libc++";
472 | CLANG_ENABLE_MODULES = YES;
473 | CLANG_ENABLE_OBJC_ARC = YES;
474 | CLANG_ENABLE_OBJC_WEAK = YES;
475 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
476 | CLANG_WARN_BOOL_CONVERSION = YES;
477 | CLANG_WARN_COMMA = YES;
478 | CLANG_WARN_CONSTANT_CONVERSION = YES;
479 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
480 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
481 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
482 | CLANG_WARN_EMPTY_BODY = YES;
483 | CLANG_WARN_ENUM_CONVERSION = YES;
484 | CLANG_WARN_INFINITE_RECURSION = YES;
485 | CLANG_WARN_INT_CONVERSION = YES;
486 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
487 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
488 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
489 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
490 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
491 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
492 | CLANG_WARN_STRICT_PROTOTYPES = YES;
493 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
494 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
495 | CLANG_WARN_UNREACHABLE_CODE = YES;
496 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
497 | COPY_PHASE_STRIP = NO;
498 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
499 | ENABLE_NS_ASSERTIONS = NO;
500 | ENABLE_STRICT_OBJC_MSGSEND = YES;
501 | GCC_C_LANGUAGE_STANDARD = gnu11;
502 | GCC_NO_COMMON_BLOCKS = YES;
503 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
504 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
505 | GCC_WARN_UNDECLARED_SELECTOR = YES;
506 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
507 | GCC_WARN_UNUSED_FUNCTION = YES;
508 | GCC_WARN_UNUSED_VARIABLE = YES;
509 | MACOSX_DEPLOYMENT_TARGET = 10.15;
510 | MTL_ENABLE_DEBUG_INFO = NO;
511 | MTL_FAST_MATH = YES;
512 | SDKROOT = macosx;
513 | SWIFT_COMPILATION_MODE = wholemodule;
514 | SWIFT_OPTIMIZATION_LEVEL = "-O";
515 | };
516 | name = Release;
517 | };
518 | 9DD3BA8324B4639700EDA308 /* Debug */ = {
519 | isa = XCBuildConfiguration;
520 | buildSettings = {
521 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
522 | CODE_SIGN_ENTITLEMENTS = XcodeCleaner/XcodeCleaner.entitlements;
523 | CODE_SIGN_IDENTITY = "-";
524 | CODE_SIGN_STYLE = Automatic;
525 | COMBINE_HIDPI_IMAGES = YES;
526 | DEVELOPMENT_ASSET_PATHS = "\"XcodeCleaner/Preview Content\"";
527 | DEVELOPMENT_TEAM = A46C2G8GT3;
528 | ENABLE_HARDENED_RUNTIME = YES;
529 | ENABLE_PREVIEWS = YES;
530 | INFOPLIST_FILE = XcodeCleaner/Info.plist;
531 | LD_RUNPATH_SEARCH_PATHS = (
532 | "$(inherited)",
533 | "@executable_path/../Frameworks",
534 | );
535 | MACOSX_DEPLOYMENT_TARGET = 10.15;
536 | MARKETING_VERSION = 1.2.2;
537 | PRODUCT_BUNDLE_IDENTIFIER = com.ireldev.XcodeCleaner.XcodeCleaner;
538 | PRODUCT_NAME = "$(TARGET_NAME)";
539 | SWIFT_VERSION = 5.0;
540 | };
541 | name = Debug;
542 | };
543 | 9DD3BA8424B4639700EDA308 /* Release */ = {
544 | isa = XCBuildConfiguration;
545 | buildSettings = {
546 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
547 | CODE_SIGN_ENTITLEMENTS = XcodeCleaner/XcodeCleaner.entitlements;
548 | CODE_SIGN_IDENTITY = "-";
549 | CODE_SIGN_STYLE = Automatic;
550 | COMBINE_HIDPI_IMAGES = YES;
551 | DEVELOPMENT_ASSET_PATHS = "\"XcodeCleaner/Preview Content\"";
552 | DEVELOPMENT_TEAM = A46C2G8GT3;
553 | ENABLE_HARDENED_RUNTIME = YES;
554 | ENABLE_PREVIEWS = YES;
555 | INFOPLIST_FILE = XcodeCleaner/Info.plist;
556 | LD_RUNPATH_SEARCH_PATHS = (
557 | "$(inherited)",
558 | "@executable_path/../Frameworks",
559 | );
560 | MACOSX_DEPLOYMENT_TARGET = 10.15;
561 | MARKETING_VERSION = 1.2.2;
562 | PRODUCT_BUNDLE_IDENTIFIER = com.ireldev.XcodeCleaner.XcodeCleaner;
563 | PRODUCT_NAME = "$(TARGET_NAME)";
564 | SWIFT_VERSION = 5.0;
565 | };
566 | name = Release;
567 | };
568 | /* End XCBuildConfiguration section */
569 |
570 | /* Begin XCConfigurationList section */
571 | 9DD3BA6724B4639400EDA308 /* Build configuration list for PBXProject "XcodeCleaner" */ = {
572 | isa = XCConfigurationList;
573 | buildConfigurations = (
574 | 9DD3BA8024B4639700EDA308 /* Debug */,
575 | 9DD3BA8124B4639700EDA308 /* Release */,
576 | );
577 | defaultConfigurationIsVisible = 0;
578 | defaultConfigurationName = Release;
579 | };
580 | 9DD3BA8224B4639700EDA308 /* Build configuration list for PBXNativeTarget "XcodeCleaner" */ = {
581 | isa = XCConfigurationList;
582 | buildConfigurations = (
583 | 9DD3BA8324B4639700EDA308 /* Debug */,
584 | 9DD3BA8424B4639700EDA308 /* Release */,
585 | );
586 | defaultConfigurationIsVisible = 0;
587 | defaultConfigurationName = Release;
588 | };
589 | /* End XCConfigurationList section */
590 |
591 | /* Begin XCVersionGroup section */
592 | 9DD3BA7324B4639400EDA308 /* XcodeCleaner.xcdatamodeld */ = {
593 | isa = XCVersionGroup;
594 | children = (
595 | 9DD3BA7424B4639400EDA308 /* XcodeCleaner.xcdatamodel */,
596 | );
597 | currentVersion = 9DD3BA7424B4639400EDA308 /* XcodeCleaner.xcdatamodel */;
598 | path = XcodeCleaner.xcdatamodeld;
599 | sourceTree = "";
600 | versionGroupType = wrapper.xcdatamodel;
601 | };
602 | /* End XCVersionGroup section */
603 | };
604 | rootObject = 9DD3BA6424B4639400EDA308 /* Project object */;
605 | }
606 |
--------------------------------------------------------------------------------
/XcodeCleaner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/XcodeCleaner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/XcodeCleaner.xcodeproj/xcuserdata/revilarva.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | XcodeCleaner.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/XcodeCleaner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 07.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SwiftUI
11 |
12 | @NSApplicationMain
13 | class AppDelegate: NSObject, NSApplicationDelegate {
14 |
15 | var window: NSWindow!
16 |
17 |
18 | func applicationDidFinishLaunching(_ aNotification: Notification) {
19 | // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
20 | // Add `@Environment(\.managedObjectContext)` in the views that will need the context.
21 | let viewModel = ViewModel()
22 | let contentView = ContentView().environment(\.managedObjectContext, persistentContainer.viewContext).environmentObject(viewModel)
23 |
24 | // Create the window and set the content view.
25 | window = NSWindow(
26 | contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
27 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
28 | backing: .buffered, defer: false)
29 | window.center()
30 | window.setFrameAutosaveName("Main Window")
31 | window.contentView = NSHostingView(rootView: contentView)
32 | window.makeKeyAndOrderFront(nil)
33 | }
34 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true }
35 |
36 | func applicationWillTerminate(_ aNotification: Notification) {
37 | // Insert code here to tear down your application
38 | }
39 |
40 | // MARK: - Core Data stack
41 |
42 | lazy var persistentContainer: NSPersistentContainer = {
43 | /*
44 | The persistent container for the application. This implementation
45 | creates and returns a container, having loaded the store for the
46 | application to it. This property is optional since there are legitimate
47 | error conditions that could cause the creation of the store to fail.
48 | */
49 | let container = NSPersistentContainer(name: "XcodeCleaner")
50 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in
51 | if let error = error {
52 | // Replace this implementation with code to handle the error appropriately.
53 | // 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.
54 |
55 | /*
56 | Typical reasons for an error here include:
57 | * The parent directory does not exist, cannot be created, or disallows writing.
58 | * The persistent store is not accessible, due to permissions or data protection when the device is locked.
59 | * The device is out of space.
60 | * The store could not be migrated to the current model version.
61 | Check the error message to determine what the actual problem was.
62 | */
63 | fatalError("Unresolved error \(error)")
64 | }
65 | })
66 | return container
67 | }()
68 |
69 | // MARK: - Core Data Saving and Undo support
70 |
71 | @IBAction func saveAction(_ sender: AnyObject?) {
72 | // 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.
73 | let context = persistentContainer.viewContext
74 |
75 | if !context.commitEditing() {
76 | NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing before saving")
77 | }
78 | if context.hasChanges {
79 | do {
80 | try context.save()
81 | } catch {
82 | // Customize this code block to include application-specific recovery steps.
83 | let nserror = error as NSError
84 | NSApplication.shared.presentError(nserror)
85 | }
86 | }
87 | }
88 |
89 | func windowWillReturnUndoManager(window: NSWindow) -> UndoManager? {
90 | // Returns the NSUndoManager for the application. In this case, the manager returned is that of the managed object context for the application.
91 | return persistentContainer.viewContext.undoManager
92 | }
93 |
94 | func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
95 | // Save changes in the application's managed object context before the application terminates.
96 | let context = persistentContainer.viewContext
97 |
98 | if !context.commitEditing() {
99 | NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing to terminate")
100 | return .terminateCancel
101 | }
102 |
103 | if !context.hasChanges {
104 | return .terminateNow
105 | }
106 |
107 | do {
108 | try context.save()
109 | } catch {
110 | let nserror = error as NSError
111 |
112 | // Customize this code block to include application-specific recovery steps.
113 | let result = sender.presentError(nserror)
114 | if (result) {
115 | return .terminateCancel
116 | }
117 |
118 | let question = NSLocalizedString("Could not save changes while quitting. Quit anyway?", comment: "Quit without saves error question message")
119 | let info = NSLocalizedString("Quitting now will lose any changes you have made since the last successful save", comment: "Quit without saves error question info");
120 | let quitButton = NSLocalizedString("Quit anyway", comment: "Quit anyway button title")
121 | let cancelButton = NSLocalizedString("Cancel", comment: "Cancel button title")
122 | let alert = NSAlert()
123 | alert.messageText = question
124 | alert.informativeText = info
125 | alert.addButton(withTitle: quitButton)
126 | alert.addButton(withTitle: cancelButton)
127 |
128 | let answer = alert.runModal()
129 | if answer == .alertSecondButtonReturn {
130 | return .terminateCancel
131 | }
132 | }
133 | // If we got here, it is time to quit.
134 | return .terminateNow
135 | }
136 |
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "mac-16x16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "mac-16x16@2x.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "mac-32x32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "mac-32x32@2x.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "mac-128x128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "mac-128x128@2x.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "mac-256x256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "mac-256x256@2x.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "mac-512x512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "mac-512x512@2x.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-128x128@2x.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-16x16@2x.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-256x256@2x.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-32x32@2x.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IrelDev/XcodeCleaner/a6eeed150cce460c1ba8b251b852ae84e4d9de79/XcodeCleaner/Assets.xcassets/AppIcon.appiconset/mac-512x512@2x.png
--------------------------------------------------------------------------------
/XcodeCleaner/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XcodeCleaner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
--------------------------------------------------------------------------------
/XcodeCleaner/CoreDataManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreDataManager.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 08.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import AppKit
10 | import CoreData
11 |
12 | struct CoreDataManager {
13 | static let shared = CoreDataManager()
14 |
15 | func getStatistic() -> StatisticModel {
16 | var statisticModel = StatisticModel()
17 | fetchStatisticIn(statisticModel: &statisticModel)
18 |
19 | return statisticModel
20 | }
21 | func saveStatistic(statistic: StatisticModel) {
22 | let newTotalCleanedValue = statistic.totalCleaned
23 | let newDate = statistic.lastTimeCleaned
24 |
25 | saveStatistic(newValue: newTotalCleanedValue, newDate: newDate)
26 | }
27 | private func fetchStatisticIn(statisticModel: inout StatisticModel) {
28 | guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return }
29 |
30 | let managedContext = appDelegate.persistentContainer.viewContext
31 | let fetchRequest = NSFetchRequest(entityName: "StatisticEntity")
32 |
33 | do {
34 | let fetchRequestResults = try managedContext.fetch(fetchRequest)
35 |
36 | let totalCleaned = fetchRequestResults.first?.value(forKey: "totalSize") as? Int64
37 | let date = fetchRequestResults.first?.value(forKey: "date") as? Date
38 |
39 | let modelFromFetchRequest = StatisticModel(totalCleaned: totalCleaned, lastTimeCleaned: date)
40 | statisticModel = modelFromFetchRequest
41 |
42 | } catch {
43 | print("could not fetch statistic \(error.localizedDescription)")
44 | }
45 | }
46 | private func saveStatistic(newValue: Int64?, newDate: Date?) {
47 | guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return }
48 | let managedContext = appDelegate.persistentContainer.viewContext
49 | let entity = NSEntityDescription.entity(forEntityName: "StatisticEntity", in: managedContext)!
50 |
51 | let fetchRequest = NSFetchRequest(entityName: "StatisticEntity")
52 |
53 | do {
54 | let fetchRequestResults = try managedContext.fetch(fetchRequest)
55 | if fetchRequestResults.count == 0 {
56 | let statistic = NSManagedObject(entity: entity, insertInto: managedContext)
57 |
58 | statistic.setValue(newValue, forKeyPath: "totalSize")
59 | statistic.setValue(newDate, forKeyPath: "date")
60 | } else {
61 | let previousStatistic = fetchRequestResults.first!
62 |
63 | let previousTotalSizeValue = previousStatistic.value(forKey: "totalSize") as! Int64
64 | let value = (newValue ?? 0) + previousTotalSizeValue
65 |
66 | previousStatistic.setValue(newDate, forKey: "date")
67 | previousStatistic.setValue(value, forKey: "totalSize")
68 | }
69 |
70 | try managedContext.save()
71 | } catch {
72 | print("Error, could not save statistic \(error.localizedDescription)")
73 | }
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/XcodeCleaner/DirectoryManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DirectoryManager.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 04.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | struct DirectoryManager {
12 | func getValidHomeDirectory() -> String {
13 | var homeDirectory = NSHomeDirectory()
14 | let prefix = "/Library/Containers/"
15 |
16 | if homeDirectory.contains(prefix) {
17 | if let range = homeDirectory.range(of: prefix) {
18 | homeDirectory = String(homeDirectory[.. String {
24 | let homeDirectory = getValidHomeDirectory()
25 | let xcodePath = "/Library/Developer/Xcode/"
26 | return "\(homeDirectory + xcodePath)"
27 | }
28 | func getDerivedDataPath() -> String {
29 | let xcodePath = getXcodeDefaultPath()
30 | let derivedDataPath = "DerivedData/"
31 | return "\(xcodePath + derivedDataPath)"
32 | }
33 | func getIOSDeviceSupportPath() -> String {
34 | let xcodePath = getXcodeDefaultPath()
35 | let deviceSupportPath = "iOS DeviceSupport/"
36 | return "\(xcodePath + deviceSupportPath)"
37 | }
38 | func getWatchOSDeviceSupportPath() -> String {
39 | let xcodePath = getXcodeDefaultPath()
40 | let watchOSDeviceSupport = "watchOS DeviceSupport/"
41 | return "\(xcodePath + watchOSDeviceSupport)"
42 | }
43 | func getArchivesPath() -> String {
44 | let xcodePath = getXcodeDefaultPath()
45 | let archivesPath = "Archives/"
46 | return "\(xcodePath + archivesPath)"
47 | }
48 | func getIOSDeviceLogsPath() -> String {
49 | let xcodePath = getXcodeDefaultPath()
50 | let iOSDeviceLogsPath = "iOS Device Logs/"
51 | return "\(xcodePath + iOSDeviceLogsPath)"
52 | }
53 | func getDocumentationCachePath() -> String {
54 | let xcodePath = getXcodeDefaultPath()
55 | let documentationCachePath = "DocumentationCache/"
56 | return "\(xcodePath + documentationCachePath)"
57 | }
58 | func getSubDirectoriesForPath(path: String) -> [String] {
59 | let fileManager = FileManager.default
60 |
61 | var subDirectories: [String] = []
62 | do {
63 | let directories = try fileManager.contentsOfDirectory(atPath: path)
64 |
65 | for directory in directories {
66 | let subDirectoryPath = path + directory
67 | subDirectories.append(subDirectoryPath)
68 | }
69 | } catch {
70 | print(error.localizedDescription)
71 | }
72 |
73 | return subDirectories
74 | }
75 | func getFileType(path: String) -> FileType? {
76 | let fileManager = FileManager.default
77 | var isDirectory: ObjCBool = false
78 |
79 | if fileManager.fileExists(atPath: path, isDirectory: &isDirectory) {
80 | if isDirectory.boolValue {
81 | return .directory
82 | } else {
83 | return .file
84 | }
85 | }
86 | return nil
87 | }
88 | func getSize(path: String, completion: () -> Void = { }) -> Int64 {
89 | let fileManager = FileManager.default
90 | var directorySize: Int64 = 0
91 |
92 | var normalizedPath: String
93 | let fileType = getFileType(path: path)
94 |
95 | switch fileType {
96 | case .directory:
97 | normalizedPath = normalizePathForDirectory(path: path)
98 | case .file:
99 | normalizedPath = normalizePathForFile(path: path)
100 | case .none:
101 | return 0
102 | }
103 |
104 | if fileType == .file {
105 | do {
106 | let attributes = try fileManager.attributesOfItem(atPath: normalizedPath)
107 | directorySize += attributes[FileAttributeKey.size] as! Int64
108 | } catch {
109 | print(error.localizedDescription)
110 | }
111 | } else {
112 | let directories = fileManager.subpaths(atPath: normalizedPath)
113 |
114 | for directory in directories! {
115 | do {
116 | let newPath = normalizedPath + directory
117 | let attributes = try fileManager.attributesOfItem(atPath: newPath)
118 | directorySize += attributes[FileAttributeKey.size] as! Int64
119 | } catch {
120 | print(error.localizedDescription)
121 | }
122 | }
123 | }
124 | completion()
125 | return directorySize
126 | }
127 | func getPath(for type: DirectoryType) -> String {
128 | var path: String
129 |
130 | switch type {
131 | case .derivedData:
132 | path = getDerivedDataPath()
133 | case .iOSDeviceSupport:
134 | path = getIOSDeviceSupportPath()
135 | case .watchOSDeviceSupport:
136 | path = getWatchOSDeviceSupportPath()
137 | case .archives:
138 | path = getArchivesPath()
139 | case .iOSDeviceLogs:
140 | path = getIOSDeviceLogsPath()
141 | case .documentationCache:
142 | path = getDocumentationCachePath()
143 | }
144 | return path
145 | }
146 | func normalizePathForDirectory(path: String) -> String {
147 | var newPath = path
148 |
149 | if !newPath.hasSuffix("/") {
150 | newPath += "/"
151 | }
152 | return newPath
153 | }
154 | func normalizePathForFile(path: String) -> String {
155 | var newPath = path
156 |
157 | if newPath.hasSuffix("/") {
158 | newPath.removeLast()
159 | }
160 | return newPath
161 | }
162 | func normalizePathForDisplay(directory: String, forType type: DirectoryType) -> String {
163 | var result = directory
164 | let prefix: String = getPath(for: type)
165 |
166 | if directory.contains(prefix) {
167 | result = directory.replacingOccurrences(of: prefix, with: "")
168 | }
169 |
170 | return result
171 | }
172 | func cleanDirectory(forType type: DirectoryType) {
173 | let fileManager = FileManager.default
174 | let directoryPath = getPath(for: type)
175 | let directoryURL = URL(fileURLWithPath: normalizePathForDirectory(path: directoryPath))
176 |
177 | do {
178 | try fileManager.removeItem(at: directoryURL)
179 | } catch {
180 | print(error.localizedDescription)
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/XcodeCleaner/Enums/DirectoryType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DirectoryType.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 05.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public enum DirectoryType: String, CaseIterable {
12 | case derivedData
13 | case iOSDeviceSupport
14 | case watchOSDeviceSupport
15 | case documentationCache
16 | case archives
17 | case iOSDeviceLogs
18 | }
19 |
--------------------------------------------------------------------------------
/XcodeCleaner/Enums/FileType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileType.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 12.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum FileType {
12 | case file
13 | case directory
14 | }
15 |
--------------------------------------------------------------------------------
/XcodeCleaner/Helpers/BytesToStringFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BytesToStringFormatter.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 06.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct BytesToStringFormatter {
12 | static func format(size: Int64, allowedUnits: ByteCountFormatter.Units = [.useKB, .useMB, .useGB]) -> String {
13 | let byteCountFormatter = ByteCountFormatter()
14 | byteCountFormatter.allowedUnits = allowedUnits
15 |
16 | return byteCountFormatter.string(fromByteCount: size)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/XcodeCleaner/Helpers/DateManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateManager.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 08.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct DateManager {
12 | static func getCurrentDate() -> Date {
13 | Date()
14 | }
15 | static func getStringDate(date: Date) -> String {
16 | let dateFormatter = DateFormatter()
17 | dateFormatter.dateStyle = .medium
18 | dateFormatter.timeStyle = .none
19 | return dateFormatter.string(from: date)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XcodeCleaner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | 1
23 | LSApplicationCategoryType
24 | public.app-category.developer-tools
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2020 Kirill Pustovalov. All rights reserved.
29 | NSMainStoryboardFile
30 | Main
31 | NSPrincipalClass
32 | NSApplication
33 | NSSupportsAutomaticTermination
34 |
35 | NSSupportsSuddenTermination
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/XcodeCleaner/Models/DirectoryModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DirectoryModel.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 04.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct DirectoryModel {
12 | var name: String
13 | var size: Int64
14 | }
15 |
--------------------------------------------------------------------------------
/XcodeCleaner/Models/StatisticModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatisticModel.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 06.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct StatisticModel {
12 | var totalCleaned: Int64?
13 | var lastTimeCleaned: Date?
14 | }
15 |
--------------------------------------------------------------------------------
/XcodeCleaner/PieChart/Models/PieChartItemModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PieChartItemModel.swift
3 | // PieChartSwiftUI
4 | //
5 | // Created by Kirill Pustovalov on 28.06.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct PieChartItemModel {
12 | public var value: Double
13 | public var color: Color
14 |
15 | public init(value: Double, color: Color) {
16 | self.value = value
17 | self.color = color
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/XcodeCleaner/PieChart/Models/PieChartObservableItemsModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PieChartObservableItemsModel.swift
3 | // PieChartSwiftUI
4 | //
5 | // Created by Kirill Pustovalov on 28.06.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public class PCItems: ObservableObject {
12 | @Published public var items: [PieChartItemModel]
13 | public init(items: [PieChartItemModel]) {
14 | self.items = items
15 | }
16 | public init(data: [Double], chartColor: Color) {
17 | self.items = data.map {
18 | PieChartItemModel(value: $0, color: chartColor)
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XcodeCleaner/PieChart/Models/PieChartSliceModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PieChartSliceModel.swift
3 | // PieChartSwiftUI
4 | //
5 | // Created by Kirill Pustovalov on 28.06.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct PieChartSliceModel {
12 | var value: Double
13 | var color: Color
14 |
15 | var startDegree: Double
16 | var endDegree: Double
17 |
18 | public init(value: Double, color: Color, startDegree: Double, endDegree: Double) {
19 | self.value = value
20 | self.color = color
21 |
22 | self.startDegree = startDegree
23 | self.endDegree = endDegree
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/XcodeCleaner/PieChart/PieChartSliceFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PieChartSliceFactory.swift
3 | // PieChartSwiftUI
4 | //
5 | // Created by Kirill Pustovalov on 28.06.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | class PieChartSliceFactory {
10 | func createPieChartSlicesFromItems(items: [PieChartItemModel], maxShapeDegree: Double, initialDegree: Double = 0.0) -> [PieChartSliceModel] {
11 | var slices: [PieChartSliceModel] = []
12 | var previousSliceEndDegree = initialDegree
13 |
14 | let maxSumSliceValue = items.reduce(0) { $0 + $1.value }
15 |
16 | for itemIndex in 0 ..< items.count {
17 | let item = items[itemIndex]
18 |
19 | let sliceStartDegree = previousSliceEndDegree
20 | let proportionalValue = item.value / maxSumSliceValue
21 |
22 | let sliceEndDegree = sliceStartDegree + proportionalValue * maxShapeDegree
23 |
24 | let slice = PieChartSliceModel(value: item.value, color: item.color, startDegree: sliceStartDegree, endDegree: sliceEndDegree)
25 | slices.append(slice)
26 |
27 | previousSliceEndDegree = sliceEndDegree
28 | }
29 | return slices
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/XcodeCleaner/PieChart/Shapes/PieChartSliceShape.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PieChartSliceShape.swift
3 | // PieChartSwiftUI
4 | //
5 | // Created by Kirill Pustovalov on 28.06.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct PieChartSliceShape: Shape {
12 | var startAngle: Angle
13 | var endAngle: Angle
14 |
15 | func path(in rect: CGRect) -> Path {
16 | var path = Path()
17 | let center = CGPoint(x: rect.midX, y: rect.midY)
18 | let size = min(rect.width / 2, rect.height / 2)
19 |
20 | let radius = size
21 |
22 | path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
23 | path.addLine(to: center)
24 |
25 | path.closeSubpath()
26 |
27 | return path
28 | }
29 | }
30 |
31 | struct PieChartSliceShape_Previews: PreviewProvider {
32 | static var previews: some View {
33 | let startAngle = Angle(degrees: 0)
34 | let endAngle = Angle(degrees: 90)
35 |
36 | return PieChartSliceShape(startAngle: startAngle, endAngle: endAngle)
37 | .fill()
38 | .foregroundColor(.orange)
39 | .frame(width: 150, height: 150)
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/XcodeCleaner/PieChart/Views/PieChartSliceView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PieChartSliceView.swift
3 | // PieChartSwiftUI
4 | //
5 | // Created by Kirill Pustovalov on 28.06.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct PieChartSliceView: View {
12 | var rect: CGRect
13 |
14 | let slice: PieChartSliceModel
15 |
16 | @State var isVisible: Bool = false
17 |
18 | var body: some View {
19 | let startAngle = Angle(degrees: slice.startDegree)
20 | let endAngle = Angle(degrees: slice.endDegree)
21 |
22 | let sliceShape = PieChartSliceShape(startAngle: startAngle, endAngle: endAngle)
23 |
24 | return sliceShape
25 | .fill()
26 | .foregroundColor(slice.color)
27 | .scaleEffect(isVisible ? 1: 0.01)
28 | .animation(Animation.easeIn)
29 | .onAppear {
30 | self.isVisible.toggle()
31 | }
32 | }
33 | }
34 |
35 | struct PieSliceView_Previews: PreviewProvider {
36 | static var previews: some View {
37 | let pieSlice = PieChartSliceModel(value: .zero, color: .orange, startDegree: 50, endDegree: 130)
38 | return GeometryReader { geometryReader in
39 | PieChartSliceView(rect: geometryReader.frame(in: .local), slice: pieSlice)
40 | }
41 | .frame(width: 150, height: 150)
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/XcodeCleaner/PieChart/Views/PieChartView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PieChartView.swift
3 | // PieChartSwiftUI
4 | //
5 | // Created by Kirill Pustovalov on 28.06.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | public struct PieChartView: View {
12 | @ObservedObject var items: PCItems
13 |
14 | public init(items: PCItems) {
15 | self.items = items
16 | }
17 |
18 | public var body: some View {
19 | let sliceFactory = PieChartSliceFactory()
20 | let circleShapeMaxDegree = 360.0
21 |
22 | let slices = sliceFactory.createPieChartSlicesFromItems(items: items.items, maxShapeDegree: circleShapeMaxDegree)
23 |
24 | return Group {
25 | GeometryReader { geometryReader in
26 | ForEach(0 ..< slices.count, id: \.self) { sliceIndex in
27 | PieChartSliceView(rect: geometryReader.frame(in: .local), slice: slices[sliceIndex])
28 | }
29 | .overlay(Circle().stroke(Color(NSColor.labelColor), lineWidth: 1))
30 | .animation(Animation.easeIn)
31 | .frame(width: geometryReader.size.width, height: geometryReader.size.height)
32 | }
33 | }
34 | }
35 | }
36 |
37 |
38 | struct PieChartView_Previews: PreviewProvider {
39 | static var previews: some View {
40 | let pieChartItemOne = PieChartItemModel(value: 25, color: .pink)
41 | let pieChartItemTwo = PieChartItemModel(value: 25, color: .orange)
42 | let pieChartItemThree = PieChartItemModel(value: 25, color: .red)
43 |
44 | let items = PCItems(items: [pieChartItemOne, pieChartItemTwo, pieChartItemThree])
45 | let itemsFromData = PCItems(data: [25, 50, 25], chartColor: .black)
46 |
47 | return VStack {
48 | Spacer()
49 |
50 | PieChartView(items: items)
51 | .frame(width: 100, height: 100, alignment: .center)
52 | Spacer()
53 |
54 | PieChartView(items: itemsFromData)
55 | .frame(width: 100, height: 100, alignment: .center)
56 | Spacer()
57 |
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/XcodeCleaner/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XcodeCleaner/Protocols/DirectoryListViewModelProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DirectoryListViewModelProtocol.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 06.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | protocol DirectoryListViewModelProtocol {
12 | var directoryName: String { get set }
13 | var totalSize: Int64 { get set }
14 | var directories: [DirectoryModel] { get set }
15 | var circleColor: Color { get set }
16 |
17 | mutating func calculateTotalSize()
18 | }
19 |
--------------------------------------------------------------------------------
/XcodeCleaner/Protocols/PieChartViewModelProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PieChartViewModelProtocol.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 07.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol PieChartViewModelProtocol {
12 | var items: [PieChartItemModel] { get set }
13 |
14 | mutating func createItems(derivedData: [DirectoryModel], iOSDeviceSupport: [DirectoryModel], watchOSDeviceSupport: [DirectoryModel], archives: [DirectoryModel], iOSDeviceLogs: [DirectoryModel], documentationCache: [DirectoryModel])
15 | func getPCItems() -> PCItems
16 | }
17 |
--------------------------------------------------------------------------------
/XcodeCleaner/Protocols/StatisticViewModelProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatisticViewModelProtocol.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 06.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol StatisticViewModelProtocol {
12 | var statistic: StatisticModel { get set }
13 |
14 | func getLastDate() -> String
15 | func getTotalSize() -> String
16 | }
17 |
--------------------------------------------------------------------------------
/XcodeCleaner/Protocols/ViewModelProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModelProtocol.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 06.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ViewModelProtocol {
12 | var isScanStarted: Bool { get set }
13 |
14 | var directoriesCount: Int { get set }
15 | var analyzedDirectoriesCount: Int { get set }
16 | var totalSize: Int64 { get set }
17 |
18 | var derivedData: [DirectoryModel] { get set }
19 | var iOSDeviceSupport: [DirectoryModel] { get set }
20 | var archives: [DirectoryModel] { get set }
21 |
22 | var scanProgress: Double { get }
23 |
24 | var isReadyToBeCleaned: Bool { get set }
25 |
26 | var isAlertPresented: Bool { get set }
27 |
28 | func startScan()
29 | func calculateSize(ofDirectory: inout [DirectoryModel], subDirectories: [String], type: DirectoryType)
30 | func getViewModelForItemList(forType type: DirectoryType) -> DirectoryListViewModelProtocol
31 | func cleanBeforeScan()
32 | func getViewModelForPieChart() -> PieChartViewModelProtocol
33 | func getViewModelForStatistic() -> StatisticViewModelProtocol
34 |
35 | func startClean()
36 | }
37 |
--------------------------------------------------------------------------------
/XcodeCleaner/ViewModels/DirectoryListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DirectoryListViewModel.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 06.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct DirectoryListViewModel: DirectoryListViewModelProtocol {
12 | var directoryName: String
13 | var directories: [DirectoryModel]
14 |
15 | var totalSize: Int64 = 0
16 | var circleColor: Color
17 |
18 | mutating func calculateTotalSize() {
19 | var size: Int64 = 0
20 |
21 | directories.forEach { directory in
22 | size += directory.size
23 | }
24 | totalSize = size
25 | }
26 | }
27 |
28 | struct DirectoryListViewModel_Previews: PreviewProvider {
29 | static var previews: some View {
30 | /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/XcodeCleaner/ViewModels/ObservableFilterModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObservableFilterModel.swift
3 | // XcodeCleaner
4 | //
5 | // Created by iMokhles on 29/10/2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import SwiftUI
11 |
12 | class ObservableFilterModel: ObservableObject {
13 |
14 | let objectWillChange = ObservableObjectPublisher()
15 |
16 | var sortMethod = 0 {
17 | willSet {
18 | objectWillChange.send()
19 | }
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/XcodeCleaner/ViewModels/PieChartViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PieChartViewModel.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 07.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct PieChartViewModel: PieChartViewModelProtocol {
12 | var items: [PieChartItemModel] = []
13 |
14 | mutating func createItems(derivedData: [DirectoryModel], iOSDeviceSupport: [DirectoryModel], watchOSDeviceSupport: [DirectoryModel], archives: [DirectoryModel], iOSDeviceLogs: [DirectoryModel], documentationCache: [DirectoryModel]) {
15 | var derivedDataSize: Int64 = 0
16 | derivedData.forEach {
17 | derivedDataSize += $0.size
18 | }
19 |
20 | var iOSDeviceSupportSize: Int64 = 0
21 | iOSDeviceSupport.forEach {
22 | iOSDeviceSupportSize += $0.size
23 | }
24 |
25 | var watchOSDeviceSupportSize: Int64 = 0
26 | watchOSDeviceSupport.forEach {
27 | watchOSDeviceSupportSize += $0.size
28 | }
29 |
30 | var archivesSize: Int64 = 0
31 | archives.forEach {
32 | archivesSize += $0.size
33 | }
34 |
35 | var iOSDeviceLogsSize: Int64 = 0
36 | iOSDeviceLogs.forEach {
37 | iOSDeviceLogsSize += $0.size
38 | }
39 |
40 | var documentationCacheSize: Int64 = 0
41 | documentationCache.forEach {
42 | documentationCacheSize += $0.size
43 | }
44 |
45 | var derivedData: PieChartItemModel
46 | var iOSDeviceSupport: PieChartItemModel
47 | var watchOSDeviceSupport: PieChartItemModel
48 | var archives: PieChartItemModel
49 | var iOSDeviceLogs: PieChartItemModel
50 | var documentationCache: PieChartItemModel
51 |
52 | if derivedDataSize == 0 && iOSDeviceSupportSize == 0 && archivesSize == 0 && iOSDeviceLogsSize == 0 && watchOSDeviceSupportSize == 0 && documentationCacheSize == 0 {
53 | let defaultValue = 1.0
54 |
55 | derivedData = PieChartItemModel(value: defaultValue, color: .pink)
56 | iOSDeviceSupport = PieChartItemModel(value: defaultValue, color: Color(.cyan))
57 | archives = PieChartItemModel(value: defaultValue, color: .orange)
58 |
59 | let items = [archives, iOSDeviceSupport, derivedData]
60 | self.items = items
61 | } else {
62 | derivedData = PieChartItemModel(value: Double(derivedDataSize), color: .pink)
63 | iOSDeviceSupport = PieChartItemModel(value: Double(iOSDeviceSupportSize), color: Color(.cyan))
64 | watchOSDeviceSupport = PieChartItemModel(value: Double(watchOSDeviceSupportSize), color: .green)
65 | archives = PieChartItemModel(value: Double(archivesSize), color: .orange)
66 | iOSDeviceLogs = PieChartItemModel(value: Double(iOSDeviceLogsSize), color: .purple)
67 | documentationCache = PieChartItemModel(value: Double(documentationCacheSize), color: .gray)
68 |
69 | let items = [archives, iOSDeviceSupport, watchOSDeviceSupport, derivedData, iOSDeviceLogs, documentationCache]
70 |
71 | var normalizedItems: [PieChartItemModel] = []
72 |
73 | items.forEach {
74 | if $0.value != 0 {
75 | normalizedItems.append($0)
76 | }
77 | }
78 | self.items = normalizedItems
79 | }
80 | }
81 | func getPCItems() -> PCItems {
82 | let pcItems = PCItems(items: [])
83 |
84 | items.forEach { item in
85 | pcItems.items.append(item)
86 | }
87 | return pcItems
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/XcodeCleaner/ViewModels/StatisticViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatisticViewModel.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 06.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct StatisticViewModel: StatisticViewModelProtocol {
12 | var statistic: StatisticModel
13 |
14 | func getLastDate() -> String {
15 | guard let date = statistic.lastTimeCleaned else { return "none" }
16 | return DateManager.getStringDate(date: date)
17 | }
18 | func getTotalSize() -> String {
19 | guard let size = statistic.totalCleaned else { return "none" }
20 | return BytesToStringFormatter.format(size: size)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/XcodeCleaner/ViewModels/ViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModel.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 05.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | class ViewModel: ObservableObject, ViewModelProtocol {
13 | private let directoryManager = DirectoryManager()
14 |
15 | var isScanStarted = false
16 |
17 | var directoriesCount: Int = 0
18 | var analyzedDirectoriesCount: Int = 0
19 | var totalSize: Int64 = 0
20 |
21 | var derivedData: [DirectoryModel] = []
22 | var iOSDeviceSupport: [DirectoryModel] = []
23 | var watchOSDeviceSupport: [DirectoryModel] = []
24 | var archives: [DirectoryModel] = []
25 | var iOSDeviceLogs: [DirectoryModel] = []
26 | var documentationCache: [DirectoryModel] = []
27 |
28 | var isReadyToBeCleaned = false
29 | var isCleanStarted = false
30 |
31 | @Published var isAlertPresented = false
32 |
33 | var scanProgress: Double {
34 | directoriesCount == 0 ? 0: Double(analyzedDirectoriesCount) / Double(directoriesCount)
35 | }
36 | func startScan() {
37 | guard !isScanStarted else { return }
38 | isScanStarted.toggle()
39 |
40 | cleanBeforeScan()
41 |
42 | let derivedDataDirectories = directoryManager.getSubDirectoriesForPath(path: directoryManager.getDerivedDataPath())
43 | directoriesCount += derivedDataDirectories.count
44 | DispatchQueue.global(qos: .userInitiated).async {
45 | self.calculateSize(ofDirectory: &self.derivedData, subDirectories: derivedDataDirectories, type: .derivedData)
46 | }
47 |
48 | let iOSDeviceSupportDirectories = directoryManager.getSubDirectoriesForPath(path: directoryManager.getIOSDeviceSupportPath())
49 | directoriesCount += iOSDeviceSupportDirectories.count
50 | DispatchQueue.global(qos: .userInitiated).async {
51 | self.calculateSize(ofDirectory: &self.iOSDeviceSupport, subDirectories: iOSDeviceSupportDirectories, type: .iOSDeviceSupport)
52 | }
53 |
54 | let watchOSDeviceSupportDirectories = directoryManager.getSubDirectoriesForPath(path: directoryManager.getWatchOSDeviceSupportPath())
55 | directoriesCount += watchOSDeviceSupportDirectories.count
56 | DispatchQueue.global(qos: .userInitiated).async {
57 | self.calculateSize(ofDirectory: &self.watchOSDeviceSupport, subDirectories: watchOSDeviceSupportDirectories, type: .watchOSDeviceSupport)
58 | }
59 |
60 | let archivesDirectories = directoryManager.getSubDirectoriesForPath(path: directoryManager.getArchivesPath())
61 | directoriesCount += archivesDirectories.count
62 | DispatchQueue.global(qos: .userInitiated).async {
63 | self.calculateSize(ofDirectory: &self.archives, subDirectories: archivesDirectories, type: .archives)
64 | }
65 |
66 | let iOSDeviceLogsDirectories = directoryManager.getSubDirectoriesForPath(path: directoryManager.getIOSDeviceLogsPath())
67 | directoriesCount += iOSDeviceLogsDirectories.count
68 | DispatchQueue.global(qos: .userInitiated).async {
69 | self.calculateSize(ofDirectory: &self.iOSDeviceLogs, subDirectories: iOSDeviceLogsDirectories, type: .iOSDeviceLogs)
70 | }
71 |
72 | let documentationCacheDirectories = directoryManager.getSubDirectoriesForPath(path: directoryManager.getDocumentationCachePath())
73 | directoriesCount += documentationCacheDirectories.count
74 | DispatchQueue.global(qos: .userInitiated).async {
75 | self.calculateSize(ofDirectory: &self.documentationCache, subDirectories: documentationCacheDirectories, type: .documentationCache)
76 | }
77 |
78 | DispatchQueue.global(qos: .userInitiated).async {
79 | self.isScanStarted.toggle()
80 | self.isReadyToBeCleaned.toggle()
81 |
82 | DispatchQueue.main.async {
83 | self.objectWillChange.send()
84 | }
85 | }
86 | }
87 | func calculateSize(ofDirectory: inout [DirectoryModel], subDirectories: [String], type: DirectoryType) {
88 | ofDirectory = subDirectories.map { path in
89 | let directorySize = self.directoryManager.getSize(path: path) {
90 | self.analyzedDirectoriesCount += 1
91 | }
92 |
93 | DispatchQueue.main.async {
94 | self.totalSize += directorySize
95 | self.objectWillChange.send()
96 | }
97 | let normalizedDirectoryPathForDisplay = directoryManager.normalizePathForDisplay(directory: path, forType: type)
98 |
99 | return DirectoryModel(name: normalizedDirectoryPathForDisplay, size: directorySize)
100 | }
101 | }
102 | func getViewModelForItemList(forType type: DirectoryType) -> DirectoryListViewModelProtocol {
103 | var directoryName: String
104 | var directories: [DirectoryModel]
105 | var circleColor: Color
106 |
107 | switch type {
108 | case .derivedData:
109 | directories = derivedData
110 | directoryName = "Derived Data"
111 | circleColor = .pink
112 | case .iOSDeviceSupport:
113 | directories = iOSDeviceSupport
114 | directoryName = "iOS Device Support"
115 | circleColor = Color(.cyan)
116 | case .watchOSDeviceSupport:
117 | directories = watchOSDeviceSupport
118 | directoryName = "watchOS Device Support"
119 | circleColor = .green
120 | case .archives:
121 | directories = archives
122 | directoryName = "Archives"
123 | circleColor = .orange
124 | case .iOSDeviceLogs:
125 | directories = iOSDeviceLogs
126 | directoryName = "Device Logs"
127 | circleColor = .purple
128 | case .documentationCache:
129 | directories = documentationCache
130 | directoryName = "Documentation Cache"
131 | circleColor = .gray
132 | }
133 |
134 | var viewModel = DirectoryListViewModel(directoryName: directoryName, directories: directories, circleColor: circleColor)
135 | viewModel.calculateTotalSize()
136 |
137 | return viewModel
138 | }
139 | func cleanBeforeScan() {
140 | directoriesCount = 0
141 | analyzedDirectoriesCount = 0
142 | totalSize = 0
143 |
144 | derivedData.removeAll()
145 | iOSDeviceSupport.removeAll()
146 | watchOSDeviceSupport.removeAll()
147 | archives.removeAll()
148 | iOSDeviceLogs.removeAll()
149 | documentationCache.removeAll()
150 |
151 | objectWillChange.send()
152 | }
153 | func getViewModelForPieChart() -> PieChartViewModelProtocol {
154 | var viewModel = PieChartViewModel()
155 | viewModel.createItems(derivedData: derivedData, iOSDeviceSupport: iOSDeviceSupport, watchOSDeviceSupport: watchOSDeviceSupport, archives: archives, iOSDeviceLogs: iOSDeviceLogs, documentationCache: documentationCache)
156 |
157 | return viewModel
158 | }
159 | //add chosen type
160 | func startClean() {
161 | guard !isCleanStarted else { return }
162 | isCleanStarted.toggle()
163 | objectWillChange.send()
164 |
165 | DispatchQueue.global(qos: .userInitiated).async {
166 | self.directoryManager.cleanDirectory(forType: .derivedData)
167 | self.directoryManager.cleanDirectory(forType: .iOSDeviceSupport)
168 | self.directoryManager.cleanDirectory(forType: .watchOSDeviceSupport)
169 | self.directoryManager.cleanDirectory(forType: .archives)
170 | self.directoryManager.cleanDirectory(forType: .iOSDeviceLogs)
171 | self.directoryManager.cleanDirectory(forType: .documentationCache)
172 |
173 | DispatchQueue.main.async {
174 | let statistic = StatisticModel(totalCleaned: self.totalSize, lastTimeCleaned: DateManager.getCurrentDate())
175 |
176 | CoreDataManager.shared.saveStatistic(statistic: statistic)
177 |
178 | self.isAlertPresented.toggle()
179 | self.isCleanStarted.toggle()
180 | self.objectWillChange.send()
181 |
182 | self.isReadyToBeCleaned.toggle()
183 | }
184 | }
185 | }
186 | func getViewModelForStatistic() -> StatisticViewModelProtocol {
187 | let statistic = CoreDataManager.shared.getStatistic()
188 | let viewModel = StatisticViewModel(statistic: statistic)
189 |
190 | return viewModel
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/XcodeCleaner/Views/ActivityIndicatorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityIndicatorView.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 13.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ActivityIndicatorView: View {
12 | @State private var isAnimating: Bool = false
13 |
14 | var body: some View {
15 | GeometryReader { (geometry: GeometryProxy) in
16 | ForEach(0 ..< 5) { index in
17 | Group {
18 | Circle()
19 | .frame(width: geometry.size.width / 5, height: geometry.size.height / 5)
20 | .scaleEffect(!self.isAnimating ? 1 - CGFloat(index) / 5 : 0.2 + CGFloat(index) / 5)
21 | .offset(y: geometry.size.width / 10 - geometry.size.height / 2)
22 | }.frame(width: geometry.size.width, height: geometry.size.height)
23 | .rotationEffect(!self.isAnimating ? .degrees(0): .degrees(360))
24 | .animation(Animation
25 | .timingCurve(0.5, 0.15 + Double(index) / 5, 0.25, 1, duration: 1.5)
26 | .repeatForever(autoreverses: false))
27 | }
28 | }
29 | .aspectRatio(1, contentMode: .fit)
30 | .onAppear {
31 | self.isAnimating = true
32 | }
33 | }
34 | }
35 |
36 | struct ActivityIndicatorView_Previews: PreviewProvider {
37 | static var previews: some View {
38 | ActivityIndicatorView()
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/XcodeCleaner/Views/BodyViews/BodyView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BodyView.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 02.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct BodyView: View {
12 | var viewModel: PieChartViewModelProtocol
13 |
14 | var body: some View {
15 | GeometryReader { globalGeometry in
16 | HStack {
17 | DirectoryListView()
18 | .frame(width: 450)
19 | GeometryReader { geometryReader in
20 | PieChartView(items: self.viewModel.getPCItems())
21 | .frame(width: globalGeometry.size.width / 2, height: globalGeometry.size.height / 2)
22 | .position(CGPoint(x: geometryReader.size.width / 2, y: geometryReader.size.height / 2))
23 | .animation(.easeIn(duration: 1.0))
24 | }
25 | }
26 | }
27 | }
28 | }
29 |
30 | struct MainView_Previews: PreviewProvider {
31 | static var previews: some View {
32 | var viewModel = PieChartViewModel()
33 | viewModel.createItems(derivedData: [DirectoryModel(name: "Test", size: 2)], iOSDeviceSupport: [DirectoryModel(name: "Test", size: 2)], watchOSDeviceSupport: [DirectoryModel(name: "Test", size: 4)], archives: [DirectoryModel(name: "Test", size: 2)], iOSDeviceLogs: [DirectoryModel(name: "Test", size: 2)], documentationCache: [DirectoryModel(name: "Test", size: 2)])
34 |
35 | return BodyView(viewModel: viewModel)
36 | .environmentObject(ViewModel())
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/XcodeCleaner/Views/BodyViews/DirectoryListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DirectoryListView.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 24.06.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct DirectoryListView: View {
12 | @EnvironmentObject var viewModel: ViewModel
13 |
14 |
15 |
16 | var body: some View {
17 | VStack(alignment: .leading) {
18 | Spacer()
19 |
20 | Group {
21 | ForEach(DirectoryType.allCases, id: \.self) { type in
22 | Group {
23 | if type != .derivedData {
24 | Spacer()
25 | }
26 | DropDownView(viewModel: self.viewModel.getViewModelForItemList(forType: type))
27 | }
28 | }
29 | }
30 |
31 | Spacer()
32 | }
33 | .padding(.horizontal)
34 | }
35 | }
36 |
37 | struct ItemsListView_Previews: PreviewProvider {
38 | static var previews: some View {
39 | DirectoryListView()
40 | .environmentObject(ViewModel())
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/XcodeCleaner/Views/BodyViews/DropDownView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DropDownView.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 04.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct DropDownView: View {
12 | @State var isExpanded = false
13 | var viewModel: DirectoryListViewModelProtocol
14 |
15 | @ObservedObject var observableFilterModel = ObservableFilterModel()
16 | var sortMethods = ["Name", "Size"]
17 |
18 | var body: some View {
19 | VStack(spacing: 5) {
20 |
21 | HStack {
22 | HStack {
23 | Text("\(viewModel.directories.count == 0 ? "": isExpanded ? "▲": "▼") \(viewModel.directoryName)")
24 | .font(.system(size: 22))
25 | .fontWeight(.heavy)
26 | Spacer()
27 |
28 | Text("\(BytesToStringFormatter.format(size: viewModel.totalSize))")
29 | .foregroundColor(viewModel.circleColor)
30 | .font(.system(size: 22))
31 | .fontWeight(.heavy)
32 |
33 | Circle()
34 | .fill(viewModel.circleColor)
35 | .frame(width: 22, height: 22)
36 | }
37 | }.onTapGesture {
38 | withAnimation {
39 | self.isExpanded.toggle()
40 | }
41 | }
42 | if viewModel.directories.count > 0 {
43 | let sortedArray = self.viewModel.directories.sorted(by: { (model1, model2) -> Bool in
44 | return (observableFilterModel.sortMethod == 0) ? model1.name.lowercased() < model2.name.lowercased() : model1.size > model2.size
45 | })
46 | if isExpanded {
47 | VStack {
48 | GeometryReader { geometryReader in
49 | List {
50 | HStack {
51 | Text("Filter by")
52 | .font(.headline)
53 | Spacer()
54 | Spacer()
55 | Picker(selection: $observableFilterModel.sortMethod, label: Text("")) {
56 | ForEach(0 ..< sortMethods.count) { index in
57 | Text(self.sortMethods[index]).tag(index)
58 | }
59 | }.pickerStyle(SegmentedPickerStyle())
60 | .frame(width: 200)
61 | .clipped()
62 | }
63 | ForEach(0 ..< sortedArray.count) { index in
64 | HStack {
65 | Text(sortedArray[index].name)
66 | .lineLimit(1)
67 | Spacer()
68 | Text("\(BytesToStringFormatter.format(size: sortedArray[index].size))")
69 | .foregroundColor(self.viewModel.circleColor)
70 | }
71 | }
72 |
73 | }
74 | .font(.body)
75 | }
76 | }
77 | }
78 | }
79 | }
80 | .animation(.easeIn(duration: 0.3))
81 | }
82 | }
83 |
84 | struct DropDownView_Previews: PreviewProvider {
85 | static var previews: some View {
86 | DropDownView(viewModel: DirectoryListViewModel(directoryName: "Test", directories: [DirectoryModel(name: "/Url", size: 15)], circleColor: .green))
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/XcodeCleaner/Views/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 07.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ContentView: View {
12 | @EnvironmentObject var viewModel: ViewModel
13 | var body: some View {
14 | ZStack {
15 | VStack {
16 | BodyView(viewModel: viewModel.getViewModelForPieChart())
17 | FooterView()
18 | }
19 | .alert(isPresented: $viewModel.isAlertPresented) {
20 | Alert(title: Text("Clean finished"), message: Text("\(BytesToStringFormatter.format(size: viewModel.totalSize)) was successfully cleaned!"), dismissButton: .default(
21 | Text("Ok"), action: {
22 | self.viewModel.cleanBeforeScan()
23 | }))}
24 | .frame(minWidth: 800, minHeight: 550)
25 |
26 | if viewModel.isCleanStarted {
27 | ActivityIndicatorView()
28 | .frame(width: 200, height: 200)
29 | .foregroundColor(Color(.labelColor))
30 | }
31 | }
32 | }
33 | }
34 |
35 | struct ContentView_Previews: PreviewProvider {
36 | static var previews: some View {
37 | ContentView()
38 | .environmentObject(ViewModel())
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/XcodeCleaner/Views/FooterViews/DividerButtonView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DividerButtonView.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 03.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct DividerButtonView: View {
12 | var divider = VStack { Divider() }
13 | @EnvironmentObject var viewModel: ViewModel
14 |
15 | var body: some View {
16 | HStack {
17 | divider
18 | Button("\(viewModel.isReadyToBeCleaned ? "Clean": "Scan")") {
19 | self.viewModel.isReadyToBeCleaned ? self.viewModel.startClean(): self.viewModel.startScan()
20 | }
21 | .cornerRadius(25)
22 | divider
23 | }
24 | }
25 | }
26 |
27 | struct DividerButtonView_Previews: PreviewProvider {
28 | static var previews: some View {
29 | DividerButtonView()
30 | .environmentObject(ViewModel())
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/XcodeCleaner/Views/FooterViews/FooterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FooterView.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 02.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct FooterView: View {
12 | @EnvironmentObject var viewModel: ViewModel
13 |
14 | var body: some View {
15 | return VStack {
16 | DividerButtonView()
17 | .offset(y: 16)
18 |
19 | HStack {
20 | ScanProgressView(progress: CGFloat(viewModel.scanProgress))
21 | .padding(10)
22 |
23 | Text("\(BytesToStringFormatter.format(size: viewModel.totalSize, allowedUnits: [.useGB]))")
24 | .font(.system(size: 22))
25 | .fontWeight(.heavy)
26 | .foregroundColor(.pink)
27 | .frame(width: 100)
28 | .lineLimit(1)
29 |
30 | StatisticView(viewModel: viewModel.getViewModelForStatistic())
31 | .padding(10)
32 | }
33 | }
34 | }
35 | }
36 |
37 | struct FooterView_Previews: PreviewProvider {
38 | static var previews: some View {
39 | FooterView()
40 | .environmentObject(ViewModel())
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/XcodeCleaner/Views/FooterViews/ScanProgressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScanProgressView.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 03.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ScanProgressView: View {
12 | var progress: CGFloat = 0.0
13 | var height: CGFloat = 8
14 | var startPoint = CGPoint(x: 0, y: 0)
15 |
16 | var body: some View {
17 | GeometryReader { geometryReader in
18 | ZStack {
19 | Path { path in
20 | path.move(to: self.startPoint)
21 | path.addRoundedRect(
22 | in: CGRect(x: self.startPoint.x, y: self.startPoint.y, width: geometryReader.size.width, height: self.height),
23 | cornerSize: CGSize(width: self.height / 2 , height: self.height / 2),
24 | style: .circular)
25 | }
26 |
27 | Path { path in
28 | path.move(to: self.startPoint)
29 | path.addRoundedRect(
30 | in: CGRect(x: self.startPoint.x, y: self.startPoint.y, width: geometryReader.size.width * self.progress, height: self.height),
31 | cornerSize: CGSize(width: self.height / 2, height: self.height / 2),
32 | style: .circular)
33 | }
34 | .fill(LinearGradient(gradient: Gradient(colors: [Color(.cyan), .pink, .orange, .purple, .gray]), startPoint: .leading, endPoint: .trailing))
35 | }
36 | .frame(width: geometryReader.size.width, height: self.height)
37 | }
38 | .frame(height: self.height)
39 | }
40 | }
41 |
42 | struct ScanProgressView_Previews: PreviewProvider {
43 | static var previews: some View {
44 | VStack {
45 | ScanProgressView()
46 | ScanProgressView(progress: 1.0)
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/XcodeCleaner/Views/FooterViews/StatisticView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatisticView.swift
3 | // XcodeCleaner
4 | //
5 | // Created by Kirill Pustovalov on 02.07.2020.
6 | // Copyright © 2020 Kirill Pustovalov. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct StatisticView: View {
12 | var viewModel: StatisticViewModelProtocol
13 | var body: some View {
14 | VStack(alignment: .trailing) {
15 | Text("Total cleaned: \(viewModel.getTotalSize())")
16 | Text("Last cleanup: \(viewModel.getLastDate())")
17 | Text("Github: IrelDev")
18 | }
19 | .font(.footnote)
20 | }
21 | }
22 |
23 | struct StatisticView_Previews: PreviewProvider {
24 | static var previews: some View {
25 | StatisticView(viewModel: StatisticViewModel(statistic: StatisticModel(totalCleaned: 250000, lastTimeCleaned: Date())))
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/XcodeCleaner/XcodeCleaner.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/XcodeCleaner/XcodeCleaner.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | _XCCurrentVersionName
6 | XcodeCleaner.xcdatamodel
7 |
8 |
9 |
--------------------------------------------------------------------------------
/XcodeCleaner/XcodeCleaner.xcdatamodeld/XcodeCleaner.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------