├── .DS_Store ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── IHaven.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcshareddata │ └── xcschemes │ │ └── IHaven.xcscheme └── xcuserdata │ └── liangxiao.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── IHaven ├── .DS_Store ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon_128x128.png │ │ ├── Icon_128x128@2x.png │ │ ├── Icon_16x16.png │ │ ├── Icon_16x16@2x.png │ │ ├── Icon_256x256.png │ │ ├── Icon_256x256@2x.png │ │ ├── Icon_32x32.png │ │ ├── Icon_32x32@2x.png │ │ ├── Icon_512x512.png │ │ └── Icon_512x512@2x.png │ ├── AppImage.imageset │ │ ├── Contents.json │ │ ├── Icon_128x128@2x.png │ │ └── Icon_32x32@2x.png │ ├── BackBtn.imageset │ │ ├── Backhoeloader-2.png │ │ ├── Backhoeloader.png │ │ └── Contents.json │ ├── Contents.json │ ├── FilterBtn.imageset │ │ ├── Contents.json │ │ ├── filter-2.png │ │ └── filter.png │ ├── ImageErrorIcon.imageset │ │ ├── Contents.json │ │ ├── Network-Error-3.png │ │ └── Network-Error-4.png │ ├── LoadingIcon.imageset │ │ ├── Contents.json │ │ ├── loading-2.png │ │ └── loading.png │ ├── MenuBarIcon.imageset │ │ ├── Contents.json │ │ ├── Icon_16x16.png │ │ ├── Icon_32x32.png │ │ └── Icon_32x32@2x.png │ ├── PreferencesButtonIcon.imageset │ │ ├── Contents.json │ │ ├── setting-2.png │ │ └── setting.png │ ├── RandomBtn.imageset │ │ ├── Contents.json │ │ ├── random-2.png │ │ └── random.png │ ├── StarBtn.imageset │ │ ├── Contents.json │ │ ├── star-2.png │ │ └── star.png │ └── tagBtn.imageset │ │ ├── Contents.json │ │ ├── tag-2.png │ │ └── tag.png ├── Base.lproj │ └── Main.storyboard ├── ContentView.swift ├── Extend │ ├── Color.swift │ └── RandomAccessCollection.swift ├── IHaven.entitlements ├── Info.plist ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Resource │ └── WallHaven │ │ ├── QueryParameter.swift │ │ ├── QueryResponse.swift │ │ ├── WallHavenImage.swift │ │ ├── WallHavenImageRepository.swift │ │ └── WallHavenMeta.swift ├── Struct │ ├── DownloadState.swift │ ├── IHavenContext.swift │ ├── ImageState.swift │ └── ViewState.swift ├── Views │ ├── .DS_Store │ ├── About │ │ ├── AboutContentView.swift │ │ └── AboutWindowController.swift │ ├── Desktop │ │ ├── Advance │ │ │ └── AdvanceContentView.swift │ │ ├── Design │ │ │ └── DesignContentView.swift │ │ ├── DesktopContentView.swift │ │ ├── DesktopMainView.swift │ │ ├── DesktopWindowController.swift │ │ ├── Hot │ │ │ └── HotContentView.swift │ │ ├── Latest │ │ │ └── LatestContentView.swift │ │ ├── Random │ │ │ └── RandomContentView.swift │ │ ├── Tags │ │ │ └── TagsContentView.swift │ │ ├── Toplist │ │ │ └── ToplistContentView.swift │ │ └── Widget │ │ │ ├── DImageRepository.swift │ │ │ └── QueryParameterContentView.swift │ ├── Filter │ │ ├── CheckableButton.swift │ │ ├── FilterContentView.swift │ │ └── FilterHeader.swift │ ├── Main │ │ ├── MainContentView.swift │ │ ├── MainFooter.swift │ │ └── MainHeader.swift │ ├── Panel │ │ ├── ImageLine.swift │ │ ├── ImagePanel.swift │ │ ├── ImageRepository.swift │ │ ├── LoadingPanel.swift │ │ └── Pagination.swift │ └── Query │ │ └── QueryContentView.swift └── testdata │ └── data.json ├── LICENSE ├── README.md └── screenshots ├── OgirinIdea.png ├── logo.png ├── snapshot-2020-06-22.png ├── timg.jpeg ├── v1.png └── v2.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/.DS_Store -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /IHaven.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE3277DC24EC10D800CCA6DC /* DImageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3277DB24EC10D800CCA6DC /* DImageRepository.swift */; }; 11 | CE357BF024D4592900D55B3E /* DownloadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE357BEF24D4592900D55B3E /* DownloadState.swift */; }; 12 | CE63696926DFE737004B5F9E /* QueryParameterContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE63696826DFE737004B5F9E /* QueryParameterContentView.swift */; }; 13 | CE63696C26E67599004B5F9E /* HotContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE63696B26E67599004B5F9E /* HotContentView.swift */; }; 14 | CE7EC86224DB07A4009CE7D7 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7EC86124DB07A4009CE7D7 /* Pagination.swift */; }; 15 | CE8176D32493CD50002565B0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8176D22493CD50002565B0 /* AppDelegate.swift */; }; 16 | CE8176D52493CD50002565B0 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8176D42493CD50002565B0 /* ContentView.swift */; }; 17 | CE8176D72493CD51002565B0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE8176D62493CD51002565B0 /* Assets.xcassets */; }; 18 | CE8176DA2493CD51002565B0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE8176D92493CD51002565B0 /* Preview Assets.xcassets */; }; 19 | CE8176DD2493CD51002565B0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE8176DB2493CD51002565B0 /* Main.storyboard */; }; 20 | CE8176E72493CDB3002565B0 /* MainHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8176E62493CDB3002565B0 /* MainHeader.swift */; }; 21 | CE85F26524AB8C57001BEC83 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE85F26424AB8C57001BEC83 /* Color.swift */; }; 22 | CEAA57A624C1D65100FA3E00 /* DesktopWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAA57A524C1D65100FA3E00 /* DesktopWindowController.swift */; }; 23 | CEAA57B224C86C6E00FA3E00 /* LatestContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAA57B124C86C6E00FA3E00 /* LatestContentView.swift */; }; 24 | CEAA57B424C86C8500FA3E00 /* ToplistContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAA57B324C86C8500FA3E00 /* ToplistContentView.swift */; }; 25 | CEAA57B624C86C9700FA3E00 /* RandomContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAA57B524C86C9700FA3E00 /* RandomContentView.swift */; }; 26 | CEAA57B824C86CAF00FA3E00 /* TagsContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAA57B724C86CAF00FA3E00 /* TagsContentView.swift */; }; 27 | CEAA57BA24C86CBF00FA3E00 /* DesignContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAA57B924C86CBF00FA3E00 /* DesignContentView.swift */; }; 28 | CEAA57BC24C86CD200FA3E00 /* AdvanceContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEAA57BB24C86CD200FA3E00 /* AdvanceContentView.swift */; }; 29 | CEDCABF02498E3A7003609C1 /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = CEDCABEF2498E3A7003609C1 /* SDWebImageSwiftUI */; }; 30 | CEDCABF32498E3F2003609C1 /* ImagePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCABF22498E3F2003609C1 /* ImagePanel.swift */; }; 31 | CEDCABF6249A442A003609C1 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = CEDCABF5249A442A003609C1 /* Alamofire */; }; 32 | CEDCAC0F249D114A003609C1 /* QueryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC09249D1149003609C1 /* QueryResponse.swift */; }; 33 | CEDCAC10249D114A003609C1 /* WallHavenMeta.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC0B249D114A003609C1 /* WallHavenMeta.swift */; }; 34 | CEDCAC11249D114A003609C1 /* WallHavenImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC0C249D114A003609C1 /* WallHavenImage.swift */; }; 35 | CEDCAC13249D114A003609C1 /* WallHavenImageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC0E249D114A003609C1 /* WallHavenImageRepository.swift */; }; 36 | CEDCAC17249D1406003609C1 /* data.json in Resources */ = {isa = PBXBuildFile; fileRef = CEDCAC16249D1406003609C1 /* data.json */; }; 37 | CEDCAC1A249F016A003609C1 /* RandomAccessCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC19249F016A003609C1 /* RandomAccessCollection.swift */; }; 38 | CEDCAC1C249F1E2D003609C1 /* ImageLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC1B249F1E2D003609C1 /* ImageLine.swift */; }; 39 | CEDCAC1E249F1ECF003609C1 /* QueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC1D249F1ECF003609C1 /* QueryParameter.swift */; }; 40 | CEDCAC22249F8A8C003609C1 /* ImageState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC21249F8A8C003609C1 /* ImageState.swift */; }; 41 | CEDCAC2524A0F025003609C1 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC2424A0F025003609C1 /* AboutWindowController.swift */; }; 42 | CEDCAC2724A0F7BE003609C1 /* AboutContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDCAC2624A0F7BE003609C1 /* AboutContentView.swift */; }; 43 | CEF82BD82494CA4900610250 /* MainFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF82BD72494CA4900610250 /* MainFooter.swift */; }; 44 | CEF82BDA2494E3DC00610250 /* FilterContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF82BD92494E3DC00610250 /* FilterContentView.swift */; }; 45 | CEF82BDE2494FD7800610250 /* FilterHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF82BDD2494FD7800610250 /* FilterHeader.swift */; }; 46 | CEF82BE02495125B00610250 /* MainContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF82BDF2495125B00610250 /* MainContentView.swift */; }; 47 | CEF82BE32495136100610250 /* ViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF82BE22495136100610250 /* ViewState.swift */; }; 48 | CEF82BE82495316400610250 /* QueryContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF82BE72495316400610250 /* QueryContentView.swift */; }; 49 | CEF82BEA2495317300610250 /* DesktopContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF82BE92495317300610250 /* DesktopContentView.swift */; }; 50 | CEFE28CD24A2486800BBFBAC /* ImageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE28CC24A2486800BBFBAC /* ImageRepository.swift */; }; 51 | CEFE28CF24A2584800BBFBAC /* IHavenContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE28CE24A2584800BBFBAC /* IHavenContext.swift */; }; 52 | CEFE28D124A4B9EC00BBFBAC /* LoadingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE28D024A4B9EC00BBFBAC /* LoadingPanel.swift */; }; 53 | CEFE28D324A8F31C00BBFBAC /* CheckableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE28D224A8F31C00BBFBAC /* CheckableButton.swift */; }; 54 | /* End PBXBuildFile section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | CE3277DB24EC10D800CCA6DC /* DImageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DImageRepository.swift; sourceTree = ""; }; 58 | CE357BEF24D4592900D55B3E /* DownloadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadState.swift; sourceTree = ""; }; 59 | CE63696826DFE737004B5F9E /* QueryParameterContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryParameterContentView.swift; sourceTree = ""; }; 60 | CE63696B26E67599004B5F9E /* HotContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotContentView.swift; sourceTree = ""; }; 61 | CE7EC86124DB07A4009CE7D7 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = ""; }; 62 | CE8176CF2493CD50002565B0 /* IHaven.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IHaven.app; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | CE8176D22493CD50002565B0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 64 | CE8176D42493CD50002565B0 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 65 | CE8176D62493CD51002565B0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 66 | CE8176D92493CD51002565B0 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 67 | CE8176DC2493CD51002565B0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 68 | CE8176DE2493CD51002565B0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 69 | CE8176DF2493CD51002565B0 /* IHaven.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IHaven.entitlements; sourceTree = ""; }; 70 | CE8176E62493CDB3002565B0 /* MainHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHeader.swift; sourceTree = ""; }; 71 | CE85F26424AB8C57001BEC83 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; 72 | CEAA57A524C1D65100FA3E00 /* DesktopWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopWindowController.swift; sourceTree = ""; }; 73 | CEAA57B124C86C6E00FA3E00 /* LatestContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestContentView.swift; sourceTree = ""; }; 74 | CEAA57B324C86C8500FA3E00 /* ToplistContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToplistContentView.swift; sourceTree = ""; }; 75 | CEAA57B524C86C9700FA3E00 /* RandomContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomContentView.swift; sourceTree = ""; }; 76 | CEAA57B724C86CAF00FA3E00 /* TagsContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsContentView.swift; sourceTree = ""; }; 77 | CEAA57B924C86CBF00FA3E00 /* DesignContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignContentView.swift; sourceTree = ""; }; 78 | CEAA57BB24C86CD200FA3E00 /* AdvanceContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvanceContentView.swift; sourceTree = ""; }; 79 | CEDCABF22498E3F2003609C1 /* ImagePanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePanel.swift; sourceTree = ""; }; 80 | CEDCAC09249D1149003609C1 /* QueryResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryResponse.swift; sourceTree = ""; }; 81 | CEDCAC0B249D114A003609C1 /* WallHavenMeta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WallHavenMeta.swift; sourceTree = ""; }; 82 | CEDCAC0C249D114A003609C1 /* WallHavenImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WallHavenImage.swift; sourceTree = ""; }; 83 | CEDCAC0E249D114A003609C1 /* WallHavenImageRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WallHavenImageRepository.swift; sourceTree = ""; }; 84 | CEDCAC16249D1406003609C1 /* data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = data.json; sourceTree = ""; }; 85 | CEDCAC19249F016A003609C1 /* RandomAccessCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomAccessCollection.swift; sourceTree = ""; }; 86 | CEDCAC1B249F1E2D003609C1 /* ImageLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLine.swift; sourceTree = ""; }; 87 | CEDCAC1D249F1ECF003609C1 /* QueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryParameter.swift; sourceTree = ""; }; 88 | CEDCAC21249F8A8C003609C1 /* ImageState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageState.swift; sourceTree = ""; }; 89 | CEDCAC2424A0F025003609C1 /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; 90 | CEDCAC2624A0F7BE003609C1 /* AboutContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutContentView.swift; sourceTree = ""; }; 91 | CEF82BD72494CA4900610250 /* MainFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFooter.swift; sourceTree = ""; }; 92 | CEF82BD92494E3DC00610250 /* FilterContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterContentView.swift; sourceTree = ""; }; 93 | CEF82BDD2494FD7800610250 /* FilterHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterHeader.swift; sourceTree = ""; }; 94 | CEF82BDF2495125B00610250 /* MainContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainContentView.swift; sourceTree = ""; }; 95 | CEF82BE22495136100610250 /* ViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewState.swift; sourceTree = ""; }; 96 | CEF82BE72495316400610250 /* QueryContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryContentView.swift; sourceTree = ""; }; 97 | CEF82BE92495317300610250 /* DesktopContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopContentView.swift; sourceTree = ""; }; 98 | CEFE28CC24A2486800BBFBAC /* ImageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRepository.swift; sourceTree = ""; }; 99 | CEFE28CE24A2584800BBFBAC /* IHavenContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IHavenContext.swift; sourceTree = ""; }; 100 | CEFE28D024A4B9EC00BBFBAC /* LoadingPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingPanel.swift; sourceTree = ""; }; 101 | CEFE28D224A8F31C00BBFBAC /* CheckableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckableButton.swift; sourceTree = ""; }; 102 | /* End PBXFileReference section */ 103 | 104 | /* Begin PBXFrameworksBuildPhase section */ 105 | CE8176CC2493CD50002565B0 /* Frameworks */ = { 106 | isa = PBXFrameworksBuildPhase; 107 | buildActionMask = 2147483647; 108 | files = ( 109 | CEDCABF02498E3A7003609C1 /* SDWebImageSwiftUI in Frameworks */, 110 | CEDCABF6249A442A003609C1 /* Alamofire in Frameworks */, 111 | ); 112 | runOnlyForDeploymentPostprocessing = 0; 113 | }; 114 | /* End PBXFrameworksBuildPhase section */ 115 | 116 | /* Begin PBXGroup section */ 117 | CE09F1B42497BD460027F01A /* Extend */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | CEDCAC19249F016A003609C1 /* RandomAccessCollection.swift */, 121 | CE85F26424AB8C57001BEC83 /* Color.swift */, 122 | ); 123 | path = Extend; 124 | sourceTree = ""; 125 | }; 126 | CE3277DA24EC108E00CCA6DC /* Widget */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | CE3277DB24EC10D800CCA6DC /* DImageRepository.swift */, 130 | CE63696826DFE737004B5F9E /* QueryParameterContentView.swift */, 131 | ); 132 | path = Widget; 133 | sourceTree = ""; 134 | }; 135 | CE63696A26E6755C004B5F9E /* Hot */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | CE63696B26E67599004B5F9E /* HotContentView.swift */, 139 | ); 140 | path = Hot; 141 | sourceTree = ""; 142 | }; 143 | CE8176C62493CD50002565B0 = { 144 | isa = PBXGroup; 145 | children = ( 146 | CE8176D12493CD50002565B0 /* IHaven */, 147 | CE8176D02493CD50002565B0 /* Products */, 148 | ); 149 | sourceTree = ""; 150 | }; 151 | CE8176D02493CD50002565B0 /* Products */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | CE8176CF2493CD50002565B0 /* IHaven.app */, 155 | ); 156 | name = Products; 157 | sourceTree = ""; 158 | }; 159 | CE8176D12493CD50002565B0 /* IHaven */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | CEDCAC18249D1480003609C1 /* testdata */, 163 | CE09F1B42497BD460027F01A /* Extend */, 164 | CEF82BE12495131D00610250 /* Struct */, 165 | CEF82BE424952AC700610250 /* Resource */, 166 | CE8176E52493CD96002565B0 /* Views */, 167 | CE8176D22493CD50002565B0 /* AppDelegate.swift */, 168 | CE8176D42493CD50002565B0 /* ContentView.swift */, 169 | CE8176D62493CD51002565B0 /* Assets.xcassets */, 170 | CE8176DB2493CD51002565B0 /* Main.storyboard */, 171 | CE8176DE2493CD51002565B0 /* Info.plist */, 172 | CE8176DF2493CD51002565B0 /* IHaven.entitlements */, 173 | CE8176D82493CD51002565B0 /* Preview Content */, 174 | ); 175 | path = IHaven; 176 | sourceTree = ""; 177 | }; 178 | CE8176D82493CD51002565B0 /* Preview Content */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | CE8176D92493CD51002565B0 /* Preview Assets.xcassets */, 182 | ); 183 | path = "Preview Content"; 184 | sourceTree = ""; 185 | }; 186 | CE8176E52493CD96002565B0 /* Views */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | CEDCAC2324A0F00D003609C1 /* About */, 190 | CEDCABF12498E3D1003609C1 /* Panel */, 191 | CEF82BE62495314200610250 /* Desktop */, 192 | CEF82BE52495313A00610250 /* Query */, 193 | CEF82BDC2494FD5800610250 /* Filter */, 194 | CEF82BDB2494FD4000610250 /* Main */, 195 | ); 196 | path = Views; 197 | sourceTree = ""; 198 | }; 199 | CEAA57AB24C86B0F00FA3E00 /* Latest */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | CEAA57B124C86C6E00FA3E00 /* LatestContentView.swift */, 203 | ); 204 | path = Latest; 205 | sourceTree = ""; 206 | }; 207 | CEAA57AC24C86B2B00FA3E00 /* Toplist */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | CEAA57B324C86C8500FA3E00 /* ToplistContentView.swift */, 211 | ); 212 | path = Toplist; 213 | sourceTree = ""; 214 | }; 215 | CEAA57AD24C86C1400FA3E00 /* Random */ = { 216 | isa = PBXGroup; 217 | children = ( 218 | CEAA57B524C86C9700FA3E00 /* RandomContentView.swift */, 219 | ); 220 | path = Random; 221 | sourceTree = ""; 222 | }; 223 | CEAA57AE24C86C1F00FA3E00 /* Tags */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | CEAA57B724C86CAF00FA3E00 /* TagsContentView.swift */, 227 | ); 228 | path = Tags; 229 | sourceTree = ""; 230 | }; 231 | CEAA57AF24C86C2800FA3E00 /* Design */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | CEAA57B924C86CBF00FA3E00 /* DesignContentView.swift */, 235 | ); 236 | path = Design; 237 | sourceTree = ""; 238 | }; 239 | CEAA57B024C86C2E00FA3E00 /* Advance */ = { 240 | isa = PBXGroup; 241 | children = ( 242 | CEAA57BB24C86CD200FA3E00 /* AdvanceContentView.swift */, 243 | ); 244 | path = Advance; 245 | sourceTree = ""; 246 | }; 247 | CEDCABF12498E3D1003609C1 /* Panel */ = { 248 | isa = PBXGroup; 249 | children = ( 250 | CEDCABF22498E3F2003609C1 /* ImagePanel.swift */, 251 | CEDCAC1B249F1E2D003609C1 /* ImageLine.swift */, 252 | CEFE28CC24A2486800BBFBAC /* ImageRepository.swift */, 253 | CEFE28D024A4B9EC00BBFBAC /* LoadingPanel.swift */, 254 | CE7EC86124DB07A4009CE7D7 /* Pagination.swift */, 255 | ); 256 | path = Panel; 257 | sourceTree = ""; 258 | }; 259 | CEDCAC0A249D114A003609C1 /* WallHaven */ = { 260 | isa = PBXGroup; 261 | children = ( 262 | CEDCAC0C249D114A003609C1 /* WallHavenImage.swift */, 263 | CEDCAC0E249D114A003609C1 /* WallHavenImageRepository.swift */, 264 | CEDCAC0B249D114A003609C1 /* WallHavenMeta.swift */, 265 | CEDCAC09249D1149003609C1 /* QueryResponse.swift */, 266 | CEDCAC1D249F1ECF003609C1 /* QueryParameter.swift */, 267 | ); 268 | path = WallHaven; 269 | sourceTree = ""; 270 | }; 271 | CEDCAC18249D1480003609C1 /* testdata */ = { 272 | isa = PBXGroup; 273 | children = ( 274 | CEDCAC16249D1406003609C1 /* data.json */, 275 | ); 276 | path = testdata; 277 | sourceTree = ""; 278 | }; 279 | CEDCAC2324A0F00D003609C1 /* About */ = { 280 | isa = PBXGroup; 281 | children = ( 282 | CEDCAC2424A0F025003609C1 /* AboutWindowController.swift */, 283 | CEDCAC2624A0F7BE003609C1 /* AboutContentView.swift */, 284 | ); 285 | path = About; 286 | sourceTree = ""; 287 | }; 288 | CEF82BDB2494FD4000610250 /* Main */ = { 289 | isa = PBXGroup; 290 | children = ( 291 | CE8176E62493CDB3002565B0 /* MainHeader.swift */, 292 | CEF82BD72494CA4900610250 /* MainFooter.swift */, 293 | CEF82BDF2495125B00610250 /* MainContentView.swift */, 294 | ); 295 | path = Main; 296 | sourceTree = ""; 297 | }; 298 | CEF82BDC2494FD5800610250 /* Filter */ = { 299 | isa = PBXGroup; 300 | children = ( 301 | CEF82BD92494E3DC00610250 /* FilterContentView.swift */, 302 | CEF82BDD2494FD7800610250 /* FilterHeader.swift */, 303 | CEFE28D224A8F31C00BBFBAC /* CheckableButton.swift */, 304 | ); 305 | path = Filter; 306 | sourceTree = ""; 307 | }; 308 | CEF82BE12495131D00610250 /* Struct */ = { 309 | isa = PBXGroup; 310 | children = ( 311 | CEF82BE22495136100610250 /* ViewState.swift */, 312 | CEDCAC21249F8A8C003609C1 /* ImageState.swift */, 313 | CEFE28CE24A2584800BBFBAC /* IHavenContext.swift */, 314 | CE357BEF24D4592900D55B3E /* DownloadState.swift */, 315 | ); 316 | path = Struct; 317 | sourceTree = ""; 318 | }; 319 | CEF82BE424952AC700610250 /* Resource */ = { 320 | isa = PBXGroup; 321 | children = ( 322 | CEDCAC0A249D114A003609C1 /* WallHaven */, 323 | ); 324 | path = Resource; 325 | sourceTree = ""; 326 | }; 327 | CEF82BE52495313A00610250 /* Query */ = { 328 | isa = PBXGroup; 329 | children = ( 330 | CEF82BE72495316400610250 /* QueryContentView.swift */, 331 | ); 332 | path = Query; 333 | sourceTree = ""; 334 | }; 335 | CEF82BE62495314200610250 /* Desktop */ = { 336 | isa = PBXGroup; 337 | children = ( 338 | CE3277DA24EC108E00CCA6DC /* Widget */, 339 | CEAA57AB24C86B0F00FA3E00 /* Latest */, 340 | CE63696A26E6755C004B5F9E /* Hot */, 341 | CEAA57AC24C86B2B00FA3E00 /* Toplist */, 342 | CEAA57AD24C86C1400FA3E00 /* Random */, 343 | CEAA57AE24C86C1F00FA3E00 /* Tags */, 344 | CEAA57AF24C86C2800FA3E00 /* Design */, 345 | CEAA57B024C86C2E00FA3E00 /* Advance */, 346 | CEF82BE92495317300610250 /* DesktopContentView.swift */, 347 | CEAA57A524C1D65100FA3E00 /* DesktopWindowController.swift */, 348 | ); 349 | path = Desktop; 350 | sourceTree = ""; 351 | }; 352 | /* End PBXGroup section */ 353 | 354 | /* Begin PBXNativeTarget section */ 355 | CE8176CE2493CD50002565B0 /* IHaven */ = { 356 | isa = PBXNativeTarget; 357 | buildConfigurationList = CE8176E22493CD51002565B0 /* Build configuration list for PBXNativeTarget "IHaven" */; 358 | buildPhases = ( 359 | CE8176CB2493CD50002565B0 /* Sources */, 360 | CE8176CC2493CD50002565B0 /* Frameworks */, 361 | CE8176CD2493CD50002565B0 /* Resources */, 362 | ); 363 | buildRules = ( 364 | ); 365 | dependencies = ( 366 | ); 367 | name = IHaven; 368 | packageProductDependencies = ( 369 | CEDCABEF2498E3A7003609C1 /* SDWebImageSwiftUI */, 370 | CEDCABF5249A442A003609C1 /* Alamofire */, 371 | ); 372 | productName = IHaven; 373 | productReference = CE8176CF2493CD50002565B0 /* IHaven.app */; 374 | productType = "com.apple.product-type.application"; 375 | }; 376 | /* End PBXNativeTarget section */ 377 | 378 | /* Begin PBXProject section */ 379 | CE8176C72493CD50002565B0 /* Project object */ = { 380 | isa = PBXProject; 381 | attributes = { 382 | LastSwiftUpdateCheck = 1150; 383 | LastUpgradeCheck = 1200; 384 | ORGANIZATIONNAME = "梁霄"; 385 | TargetAttributes = { 386 | CE8176CE2493CD50002565B0 = { 387 | CreatedOnToolsVersion = 11.5; 388 | }; 389 | }; 390 | }; 391 | buildConfigurationList = CE8176CA2493CD50002565B0 /* Build configuration list for PBXProject "IHaven" */; 392 | compatibilityVersion = "Xcode 9.3"; 393 | developmentRegion = en; 394 | hasScannedForEncodings = 0; 395 | knownRegions = ( 396 | en, 397 | Base, 398 | ); 399 | mainGroup = CE8176C62493CD50002565B0; 400 | packageReferences = ( 401 | CEDCABEE2498E3A7003609C1 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */, 402 | CEDCABF4249A442A003609C1 /* XCRemoteSwiftPackageReference "Alamofire" */, 403 | ); 404 | productRefGroup = CE8176D02493CD50002565B0 /* Products */; 405 | projectDirPath = ""; 406 | projectRoot = ""; 407 | targets = ( 408 | CE8176CE2493CD50002565B0 /* IHaven */, 409 | ); 410 | }; 411 | /* End PBXProject section */ 412 | 413 | /* Begin PBXResourcesBuildPhase section */ 414 | CE8176CD2493CD50002565B0 /* Resources */ = { 415 | isa = PBXResourcesBuildPhase; 416 | buildActionMask = 2147483647; 417 | files = ( 418 | CE8176DD2493CD51002565B0 /* Main.storyboard in Resources */, 419 | CE8176DA2493CD51002565B0 /* Preview Assets.xcassets in Resources */, 420 | CEDCAC17249D1406003609C1 /* data.json in Resources */, 421 | CE8176D72493CD51002565B0 /* Assets.xcassets in Resources */, 422 | ); 423 | runOnlyForDeploymentPostprocessing = 0; 424 | }; 425 | /* End PBXResourcesBuildPhase section */ 426 | 427 | /* Begin PBXSourcesBuildPhase section */ 428 | CE8176CB2493CD50002565B0 /* Sources */ = { 429 | isa = PBXSourcesBuildPhase; 430 | buildActionMask = 2147483647; 431 | files = ( 432 | CE8176D52493CD50002565B0 /* ContentView.swift in Sources */, 433 | CEDCABF32498E3F2003609C1 /* ImagePanel.swift in Sources */, 434 | CEAA57BA24C86CBF00FA3E00 /* DesignContentView.swift in Sources */, 435 | CEDCAC0F249D114A003609C1 /* QueryResponse.swift in Sources */, 436 | CEF82BEA2495317300610250 /* DesktopContentView.swift in Sources */, 437 | CEDCAC13249D114A003609C1 /* WallHavenImageRepository.swift in Sources */, 438 | CEAA57B224C86C6E00FA3E00 /* LatestContentView.swift in Sources */, 439 | CE8176E72493CDB3002565B0 /* MainHeader.swift in Sources */, 440 | CE63696C26E67599004B5F9E /* HotContentView.swift in Sources */, 441 | CEAA57A624C1D65100FA3E00 /* DesktopWindowController.swift in Sources */, 442 | CEDCAC1A249F016A003609C1 /* RandomAccessCollection.swift in Sources */, 443 | CEF82BD82494CA4900610250 /* MainFooter.swift in Sources */, 444 | CE8176D32493CD50002565B0 /* AppDelegate.swift in Sources */, 445 | CE63696926DFE737004B5F9E /* QueryParameterContentView.swift in Sources */, 446 | CEF82BDE2494FD7800610250 /* FilterHeader.swift in Sources */, 447 | CE357BF024D4592900D55B3E /* DownloadState.swift in Sources */, 448 | CEDCAC10249D114A003609C1 /* WallHavenMeta.swift in Sources */, 449 | CEFE28D124A4B9EC00BBFBAC /* LoadingPanel.swift in Sources */, 450 | CEF82BDA2494E3DC00610250 /* FilterContentView.swift in Sources */, 451 | CE7EC86224DB07A4009CE7D7 /* Pagination.swift in Sources */, 452 | CEFE28D324A8F31C00BBFBAC /* CheckableButton.swift in Sources */, 453 | CEAA57BC24C86CD200FA3E00 /* AdvanceContentView.swift in Sources */, 454 | CE3277DC24EC10D800CCA6DC /* DImageRepository.swift in Sources */, 455 | CEDCAC11249D114A003609C1 /* WallHavenImage.swift in Sources */, 456 | CEF82BE32495136100610250 /* ViewState.swift in Sources */, 457 | CEF82BE82495316400610250 /* QueryContentView.swift in Sources */, 458 | CE85F26524AB8C57001BEC83 /* Color.swift in Sources */, 459 | CEAA57B824C86CAF00FA3E00 /* TagsContentView.swift in Sources */, 460 | CEDCAC22249F8A8C003609C1 /* ImageState.swift in Sources */, 461 | CEF82BE02495125B00610250 /* MainContentView.swift in Sources */, 462 | CEDCAC2724A0F7BE003609C1 /* AboutContentView.swift in Sources */, 463 | CEDCAC1E249F1ECF003609C1 /* QueryParameter.swift in Sources */, 464 | CEAA57B424C86C8500FA3E00 /* ToplistContentView.swift in Sources */, 465 | CEAA57B624C86C9700FA3E00 /* RandomContentView.swift in Sources */, 466 | CEDCAC2524A0F025003609C1 /* AboutWindowController.swift in Sources */, 467 | CEFE28CD24A2486800BBFBAC /* ImageRepository.swift in Sources */, 468 | CEFE28CF24A2584800BBFBAC /* IHavenContext.swift in Sources */, 469 | CEDCAC1C249F1E2D003609C1 /* ImageLine.swift in Sources */, 470 | ); 471 | runOnlyForDeploymentPostprocessing = 0; 472 | }; 473 | /* End PBXSourcesBuildPhase section */ 474 | 475 | /* Begin PBXVariantGroup section */ 476 | CE8176DB2493CD51002565B0 /* Main.storyboard */ = { 477 | isa = PBXVariantGroup; 478 | children = ( 479 | CE8176DC2493CD51002565B0 /* Base */, 480 | ); 481 | name = Main.storyboard; 482 | sourceTree = ""; 483 | }; 484 | /* End PBXVariantGroup section */ 485 | 486 | /* Begin XCBuildConfiguration section */ 487 | CE8176E02493CD51002565B0 /* Debug */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | ALWAYS_SEARCH_USER_PATHS = NO; 491 | CLANG_ANALYZER_NONNULL = YES; 492 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 493 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 494 | CLANG_CXX_LIBRARY = "libc++"; 495 | CLANG_ENABLE_MODULES = YES; 496 | CLANG_ENABLE_OBJC_ARC = YES; 497 | CLANG_ENABLE_OBJC_WEAK = YES; 498 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 499 | CLANG_WARN_BOOL_CONVERSION = YES; 500 | CLANG_WARN_COMMA = YES; 501 | CLANG_WARN_CONSTANT_CONVERSION = YES; 502 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 503 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 504 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 505 | CLANG_WARN_EMPTY_BODY = YES; 506 | CLANG_WARN_ENUM_CONVERSION = YES; 507 | CLANG_WARN_INFINITE_RECURSION = YES; 508 | CLANG_WARN_INT_CONVERSION = YES; 509 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 510 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 511 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 512 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 513 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 514 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 515 | CLANG_WARN_STRICT_PROTOTYPES = YES; 516 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 517 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 518 | CLANG_WARN_UNREACHABLE_CODE = YES; 519 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 520 | COPY_PHASE_STRIP = NO; 521 | DEBUG_INFORMATION_FORMAT = dwarf; 522 | ENABLE_STRICT_OBJC_MSGSEND = YES; 523 | ENABLE_TESTABILITY = YES; 524 | GCC_C_LANGUAGE_STANDARD = gnu11; 525 | GCC_DYNAMIC_NO_PIC = NO; 526 | GCC_NO_COMMON_BLOCKS = YES; 527 | GCC_OPTIMIZATION_LEVEL = 0; 528 | GCC_PREPROCESSOR_DEFINITIONS = ( 529 | "DEBUG=1", 530 | "$(inherited)", 531 | ); 532 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 533 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 534 | GCC_WARN_UNDECLARED_SELECTOR = YES; 535 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 536 | GCC_WARN_UNUSED_FUNCTION = YES; 537 | GCC_WARN_UNUSED_VARIABLE = YES; 538 | MACOSX_DEPLOYMENT_TARGET = 11.0; 539 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 540 | MTL_FAST_MATH = YES; 541 | ONLY_ACTIVE_ARCH = YES; 542 | SDKROOT = macosx; 543 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 544 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 545 | }; 546 | name = Debug; 547 | }; 548 | CE8176E12493CD51002565B0 /* Release */ = { 549 | isa = XCBuildConfiguration; 550 | buildSettings = { 551 | ALWAYS_SEARCH_USER_PATHS = NO; 552 | CLANG_ANALYZER_NONNULL = YES; 553 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 554 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 555 | CLANG_CXX_LIBRARY = "libc++"; 556 | CLANG_ENABLE_MODULES = YES; 557 | CLANG_ENABLE_OBJC_ARC = YES; 558 | CLANG_ENABLE_OBJC_WEAK = YES; 559 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 560 | CLANG_WARN_BOOL_CONVERSION = YES; 561 | CLANG_WARN_COMMA = YES; 562 | CLANG_WARN_CONSTANT_CONVERSION = YES; 563 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 564 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 565 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 566 | CLANG_WARN_EMPTY_BODY = YES; 567 | CLANG_WARN_ENUM_CONVERSION = YES; 568 | CLANG_WARN_INFINITE_RECURSION = YES; 569 | CLANG_WARN_INT_CONVERSION = YES; 570 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 571 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 572 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 573 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 574 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 575 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 576 | CLANG_WARN_STRICT_PROTOTYPES = YES; 577 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 578 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 579 | CLANG_WARN_UNREACHABLE_CODE = YES; 580 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 581 | COPY_PHASE_STRIP = NO; 582 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 583 | ENABLE_NS_ASSERTIONS = NO; 584 | ENABLE_STRICT_OBJC_MSGSEND = YES; 585 | GCC_C_LANGUAGE_STANDARD = gnu11; 586 | GCC_NO_COMMON_BLOCKS = YES; 587 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 588 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 589 | GCC_WARN_UNDECLARED_SELECTOR = YES; 590 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 591 | GCC_WARN_UNUSED_FUNCTION = YES; 592 | GCC_WARN_UNUSED_VARIABLE = YES; 593 | MACOSX_DEPLOYMENT_TARGET = 11.0; 594 | MTL_ENABLE_DEBUG_INFO = NO; 595 | MTL_FAST_MATH = YES; 596 | SDKROOT = macosx; 597 | SWIFT_COMPILATION_MODE = wholemodule; 598 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 599 | }; 600 | name = Release; 601 | }; 602 | CE8176E32493CD51002565B0 /* Debug */ = { 603 | isa = XCBuildConfiguration; 604 | buildSettings = { 605 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 606 | CODE_SIGN_ENTITLEMENTS = IHaven/IHaven.entitlements; 607 | CODE_SIGN_IDENTITY = "Apple Development"; 608 | CODE_SIGN_STYLE = Automatic; 609 | COMBINE_HIDPI_IMAGES = YES; 610 | DEVELOPMENT_ASSET_PATHS = "\"IHaven/Preview Content\""; 611 | DEVELOPMENT_TEAM = 4U74V57SHJ; 612 | ENABLE_HARDENED_RUNTIME = YES; 613 | ENABLE_PREVIEWS = YES; 614 | INFOPLIST_FILE = IHaven/Info.plist; 615 | LD_RUNPATH_SEARCH_PATHS = ( 616 | "$(inherited)", 617 | "@executable_path/../Frameworks", 618 | ); 619 | MACOSX_DEPLOYMENT_TARGET = 11.0; 620 | PRODUCT_BUNDLE_IDENTIFIER = cn.laxse.IHaven; 621 | PRODUCT_NAME = "$(TARGET_NAME)"; 622 | SWIFT_VERSION = 5.0; 623 | }; 624 | name = Debug; 625 | }; 626 | CE8176E42493CD51002565B0 /* Release */ = { 627 | isa = XCBuildConfiguration; 628 | buildSettings = { 629 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 630 | CODE_SIGN_ENTITLEMENTS = IHaven/IHaven.entitlements; 631 | CODE_SIGN_IDENTITY = "Apple Development"; 632 | CODE_SIGN_STYLE = Automatic; 633 | COMBINE_HIDPI_IMAGES = YES; 634 | DEVELOPMENT_ASSET_PATHS = "\"IHaven/Preview Content\""; 635 | DEVELOPMENT_TEAM = 4U74V57SHJ; 636 | ENABLE_HARDENED_RUNTIME = YES; 637 | ENABLE_PREVIEWS = YES; 638 | INFOPLIST_FILE = IHaven/Info.plist; 639 | LD_RUNPATH_SEARCH_PATHS = ( 640 | "$(inherited)", 641 | "@executable_path/../Frameworks", 642 | ); 643 | MACOSX_DEPLOYMENT_TARGET = 11.0; 644 | PRODUCT_BUNDLE_IDENTIFIER = cn.laxse.IHaven; 645 | PRODUCT_NAME = "$(TARGET_NAME)"; 646 | SWIFT_VERSION = 5.0; 647 | }; 648 | name = Release; 649 | }; 650 | /* End XCBuildConfiguration section */ 651 | 652 | /* Begin XCConfigurationList section */ 653 | CE8176CA2493CD50002565B0 /* Build configuration list for PBXProject "IHaven" */ = { 654 | isa = XCConfigurationList; 655 | buildConfigurations = ( 656 | CE8176E02493CD51002565B0 /* Debug */, 657 | CE8176E12493CD51002565B0 /* Release */, 658 | ); 659 | defaultConfigurationIsVisible = 0; 660 | defaultConfigurationName = Release; 661 | }; 662 | CE8176E22493CD51002565B0 /* Build configuration list for PBXNativeTarget "IHaven" */ = { 663 | isa = XCConfigurationList; 664 | buildConfigurations = ( 665 | CE8176E32493CD51002565B0 /* Debug */, 666 | CE8176E42493CD51002565B0 /* Release */, 667 | ); 668 | defaultConfigurationIsVisible = 0; 669 | defaultConfigurationName = Release; 670 | }; 671 | /* End XCConfigurationList section */ 672 | 673 | /* Begin XCRemoteSwiftPackageReference section */ 674 | CEDCABEE2498E3A7003609C1 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */ = { 675 | isa = XCRemoteSwiftPackageReference; 676 | repositoryURL = "https://github.com/SDWebImage/SDWebImageSwiftUI"; 677 | requirement = { 678 | kind = exactVersion; 679 | version = 2.2.6; 680 | }; 681 | }; 682 | CEDCABF4249A442A003609C1 /* XCRemoteSwiftPackageReference "Alamofire" */ = { 683 | isa = XCRemoteSwiftPackageReference; 684 | repositoryURL = "https://github.com/Alamofire/Alamofire"; 685 | requirement = { 686 | kind = exactVersion; 687 | version = 5.8.1; 688 | }; 689 | }; 690 | /* End XCRemoteSwiftPackageReference section */ 691 | 692 | /* Begin XCSwiftPackageProductDependency section */ 693 | CEDCABEF2498E3A7003609C1 /* SDWebImageSwiftUI */ = { 694 | isa = XCSwiftPackageProductDependency; 695 | package = CEDCABEE2498E3A7003609C1 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; 696 | productName = SDWebImageSwiftUI; 697 | }; 698 | CEDCABF5249A442A003609C1 /* Alamofire */ = { 699 | isa = XCSwiftPackageProductDependency; 700 | package = CEDCABF4249A442A003609C1 /* XCRemoteSwiftPackageReference "Alamofire" */; 701 | productName = Alamofire; 702 | }; 703 | /* End XCSwiftPackageProductDependency section */ 704 | }; 705 | rootObject = CE8176C72493CD50002565B0 /* Project object */; 706 | } 707 | -------------------------------------------------------------------------------- /IHaven.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /IHaven.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /IHaven.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "alamofire", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/Alamofire/Alamofire", 7 | "state" : { 8 | "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad", 9 | "version" : "5.8.1" 10 | } 11 | }, 12 | { 13 | "identity" : "sdwebimage", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/SDWebImage/SDWebImage.git", 16 | "state" : { 17 | "revision" : "e278c13e46e8d20c895c221e922c6ac6b72aaca9", 18 | "version" : "5.18.7" 19 | } 20 | }, 21 | { 22 | "identity" : "sdwebimageswiftui", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/SDWebImage/SDWebImageSwiftUI", 25 | "state" : { 26 | "revision" : "261b6cec35686d2dc192b809ab50742b4502a73b", 27 | "version" : "2.2.6" 28 | } 29 | } 30 | ], 31 | "version" : 2 32 | } 33 | -------------------------------------------------------------------------------- /IHaven.xcodeproj/xcshareddata/xcschemes/IHaven.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /IHaven.xcodeproj/xcuserdata/liangxiao.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | IHaven.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | CE8176CE2493CD50002565B0 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /IHaven/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/.DS_Store -------------------------------------------------------------------------------- /IHaven/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/9. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SwiftUI 11 | 12 | @NSApplicationMain 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | static var shared:AppDelegate? 15 | 16 | 17 | 18 | let popoverHeight:CGFloat = 500.0 19 | let popoverWidth:CGFloat = 400.0 20 | 21 | //window or view 22 | var iHavenPopover: NSPopover! 23 | var iHavenBarItem: NSStatusItem! 24 | lazy var aboutWindowController = AboutWindowController() 25 | lazy var desktopWindowController = DesktopWindowController() 26 | 27 | @ObservedObject var iHavenContext = IHavenContext() 28 | override init() { 29 | super.init() 30 | AppDelegate.shared = self; 31 | } 32 | 33 | func applicationDidFinishLaunching(_ aNotification: Notification) { 34 | // Create the popover 35 | iHavenPopover = NSPopover() 36 | iHavenPopover.contentSize = NSSize(width: popoverWidth, height: popoverHeight) 37 | iHavenPopover.behavior = .transient 38 | iHavenPopover.contentViewController = NSHostingController(rootView: ContentView().environmentObject(iHavenContext)) 39 | 40 | // Create the status item 41 | self.iHavenBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength)) 42 | 43 | if let button = self.iHavenBarItem.button { 44 | button.image = NSImage(named: "MenuBarIcon") 45 | button.action = #selector(togglePopover(_:)) 46 | } 47 | // MARK 48 | // desktopWindowController.showWindow(nil) 49 | 50 | } 51 | 52 | 53 | func applicationWillTerminate(_ aNotification: Notification) { 54 | // Insert code here to tear down your application 55 | } 56 | 57 | func showAboutWindow() { 58 | aboutWindowController.showWindow(nil) 59 | NSApplication.shared.activate(ignoringOtherApps: true) 60 | 61 | } 62 | func showDesktopWindow() { 63 | desktopWindowController.showWindow(nil) 64 | // this line may set Toolbar cannot click 65 | // NSApplication.shared.activate(ignoringOtherApps: true) 66 | 67 | //default mode is Agent(info.plist) 68 | //when show Desktop window Dynactic change to show icon on dock 69 | NSApp.setActivationPolicy(.regular) 70 | NSApp.activate(ignoringOtherApps: true) 71 | } 72 | @objc func togglePopover(_ sender: AnyObject?) { 73 | if let button = self.iHavenBarItem.button { 74 | if self.iHavenPopover.isShown { 75 | self.iHavenPopover.performClose(sender) 76 | } else { 77 | self.iHavenPopover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) 78 | } 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "Icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "Icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "Icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "Icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "Icon_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 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_32x32@2x.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Icon_128x128@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppImage.imageset/Icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppImage.imageset/Icon_128x128@2x.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/AppImage.imageset/Icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/AppImage.imageset/Icon_32x32@2x.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/BackBtn.imageset/Backhoeloader-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/BackBtn.imageset/Backhoeloader-2.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/BackBtn.imageset/Backhoeloader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/BackBtn.imageset/Backhoeloader.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/BackBtn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Backhoeloader.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Backhoeloader-2.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/FilterBtn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "filter.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "filter-2.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/FilterBtn.imageset/filter-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/FilterBtn.imageset/filter-2.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/FilterBtn.imageset/filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/FilterBtn.imageset/filter.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/ImageErrorIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Network-Error-4.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Network-Error-3.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/ImageErrorIcon.imageset/Network-Error-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/ImageErrorIcon.imageset/Network-Error-3.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/ImageErrorIcon.imageset/Network-Error-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/ImageErrorIcon.imageset/Network-Error-4.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/LoadingIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "loading.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "loading-2.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/LoadingIcon.imageset/loading-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/LoadingIcon.imageset/loading-2.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/LoadingIcon.imageset/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/LoadingIcon.imageset/loading.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/MenuBarIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_16x16.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Icon_32x32.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Icon_32x32@2x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/MenuBarIcon.imageset/Icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/MenuBarIcon.imageset/Icon_16x16.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/MenuBarIcon.imageset/Icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/MenuBarIcon.imageset/Icon_32x32.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/MenuBarIcon.imageset/Icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/MenuBarIcon.imageset/Icon_32x32@2x.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/PreferencesButtonIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "setting-2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "setting.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/PreferencesButtonIcon.imageset/setting-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/PreferencesButtonIcon.imageset/setting-2.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/PreferencesButtonIcon.imageset/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/PreferencesButtonIcon.imageset/setting.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/RandomBtn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "random.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "random-2.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/RandomBtn.imageset/random-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/RandomBtn.imageset/random-2.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/RandomBtn.imageset/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/RandomBtn.imageset/random.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/StarBtn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "star.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "star-2.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/StarBtn.imageset/star-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/StarBtn.imageset/star-2.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/StarBtn.imageset/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/StarBtn.imageset/star.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/tagBtn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "tag.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "tag-2.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/tagBtn.imageset/tag-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/tagBtn.imageset/tag-2.png -------------------------------------------------------------------------------- /IHaven/Assets.xcassets/tagBtn.imageset/tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Assets.xcassets/tagBtn.imageset/tag.png -------------------------------------------------------------------------------- /IHaven/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/9. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | struct ContentView: View { 13 | // /* currentView用来控制当前显示页面 */ 14 | @EnvironmentObject var iHavenContext: IHavenContext 15 | //未研究ViewBuilder的作用,能防止precondition failure: attribute failed to set an initial value: 87 16 | //@see https://stackoverflow.com/questions/60028961/swiftui-crash-precondition-failure-attribute-failed-to-set-an-initial-value 17 | @ViewBuilder 18 | var body: some View { 19 | ZStack { 20 | if iHavenContext.currentState == .Main { 21 | MainContentView() 22 | .transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .leading))) 23 | .edgesIgnoringSafeArea(.all) 24 | .background(Color.black.opacity(0.5)) 25 | .frame(maxWidth: .infinity, maxHeight: .infinity) 26 | } 27 | 28 | if iHavenContext.currentState == .Filter { 29 | FilterContentView() 30 | .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .trailing))) 31 | .edgesIgnoringSafeArea(.all) 32 | .background(Color.black.opacity(0.5)) 33 | .frame(maxWidth: .infinity, maxHeight: .infinity) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /IHaven/Extend/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/30. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | import SwiftUI 9 | extension Color { 10 | init(hex: String) { 11 | let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 12 | var int: UInt64 = 0 13 | Scanner(string: hex).scanHexInt64(&int) 14 | let a, r, g, b: UInt64 15 | switch hex.count { 16 | case 3: // RGB (12-bit) 17 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 18 | case 6: // RGB (24-bit) 19 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 20 | case 8: // ARGB (32-bit) 21 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 22 | default: 23 | (a, r, g, b) = (1, 1, 1, 0) 24 | } 25 | 26 | self.init( 27 | .sRGB, 28 | red: Double(r) / 255, 29 | green: Double(g) / 255, 30 | blue: Double(b) / 255, 31 | opacity: Double(a) / 255 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /IHaven/Extend/RandomAccessCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomAccessCollection.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/21. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | extension RandomAccessCollection where Self.Element: Identifiable { 10 | func isLastItem(_ item: Item) -> Bool { 11 | guard !isEmpty else { 12 | return false 13 | } 14 | 15 | guard let itemIndex = firstIndex(where: { $0.id.hashValue == item.id.hashValue }) else { 16 | return false 17 | } 18 | 19 | let distance = self.distance(from: itemIndex, to: endIndex) 20 | return distance == 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IHaven/IHaven.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.downloads.read-write 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.network.server 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /IHaven/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSUIElement 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2020 梁霄. All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | NSSupportsAutomaticTermination 34 | 35 | NSSupportsSuddenTermination 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /IHaven/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /IHaven/Resource/WallHaven/QueryParameter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryParameter.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/21. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | class QueryParameter { 11 | static let UNSET = "UNSET" 12 | static let NULL = "NULL" 13 | let q:String = "" 14 | 15 | /** 100/101/111/etc.. (general/anime/people) */ 16 | var categories:String = "011" 17 | 18 | /** 100* /110/111/etc (sfw/sketchy/nsfw) */ 19 | var purity:String = "100" 20 | 21 | static let sortingArray = [UNSET,"date_added","relevance","random","views","favorites","toplist"] 22 | /** date_added* , relevance, random, views, favorites, toplist */ 23 | var sorting:String = "date_added" 24 | 25 | /** desc asc */ 26 | let order:String = "desc" 27 | 28 | /** 1d, 3d, 1w,1M*, 3M, 6M, 1y */ 29 | let topPange:String = "1M" 30 | 31 | /** 1920x1080 */ 32 | let atleast:String = "" 33 | 34 | static let resolutionArray = [UNSET,"1280x720","1280x800","1280x960","1280x1024","1600x900","1600x1000", 35 | "1600x1200","1600x1280","1920x1080","1920x1200","1920x1440","1920x1536", 36 | "2560x1080", "2560x1440","2560x1600","2560x1920","2560x2048","3440x1440", 37 | "3840x1600","3840x2160","3840x2400","3840x2880","3840x3072"] 38 | 39 | 40 | /** 1920x1080,1920x1200 */ 41 | var resolutions:String = "" 42 | 43 | static let ratioArray = [UNSET,"9x16","10x16","9x18", "1x1","3x2","4x3","5x4", "16x9","16x10", "21x9","32x9","48x9"] 44 | /** 16x9,16x10 */ 45 | let ratios:String = "" 46 | 47 | /** 660000..... */ 48 | var color:String = "NULL" 49 | 50 | /** */ 51 | var page:Int = 1 52 | 53 | /** */ 54 | var seed:String = "" 55 | 56 | let apikey = "CmQyjMpXsdgTlyoME98eaf64cbJCRzac" 57 | 58 | init() { 59 | 60 | } 61 | init(sorting:String) { 62 | self.sorting = sorting 63 | } 64 | func _isUnset(param:String) -> Bool { 65 | if(param == QueryParameter.UNSET || param == QueryParameter.NULL || param == ""){ 66 | return true 67 | } 68 | return false 69 | } 70 | 71 | func toDictionary() -> [String: Any] { 72 | var result:[String:Any] = [:] 73 | result["q"] = q; 74 | result["categories"] = categories; 75 | result["purity"] = purity; 76 | 77 | if(!_isUnset(param: sorting)){ 78 | result["sorting"] = sorting; 79 | } 80 | if(!_isUnset(param: sorting)){ 81 | result["order"] = order; 82 | } 83 | 84 | result["topPange"] = topPange; 85 | 86 | if(!_isUnset(param: atleast)){ 87 | result["atleast"] = atleast; 88 | } 89 | if(!_isUnset(param: atleast)){ 90 | result["resolutions"] = resolutions; 91 | } 92 | if(!_isUnset(param: ratios)){ 93 | result["ratios"] = ratios; 94 | } 95 | if(!_isUnset(param: color)){ 96 | result["color"] = color; 97 | } 98 | result["page"] = page; 99 | if(seed != ""){ 100 | result["seed"] = seed; 101 | } 102 | result["apikey"] = apikey; 103 | return result 104 | } 105 | 106 | func setCategory(i:Int,v:Bool){ 107 | self.categories.replaceSubrange( 108 | self.categories.index(self.categories.startIndex, offsetBy: i)...self.categories.index(self.categories.startIndex, offsetBy: i), with: v ? "1" : "0") 109 | } 110 | func getCategory(i:Int) -> Bool { 111 | self.categories[self.categories.index(self.categories.startIndex, offsetBy: i)] == "1" 112 | } 113 | func setPurity(i:Int,v:Bool){ 114 | self.purity.replaceSubrange( 115 | self.purity.index(self.purity.startIndex, offsetBy: i)...self.purity.index(self.purity.startIndex, offsetBy: i), with: v ? "1" : "0") 116 | } 117 | func getPurity(i:Int) -> Bool { 118 | self.purity[self.purity.index(self.purity.startIndex, offsetBy: i)] == "1" 119 | } 120 | func setColor(color:String) { 121 | self.color = color 122 | } 123 | func getSortIndex() -> Double { 124 | if let index = QueryParameter.sortingArray.firstIndex(of: self.sorting) { 125 | return Double(index) 126 | }else{ 127 | return Double(0) 128 | } 129 | 130 | } 131 | func setSorting(v:Int) { 132 | self.sorting = QueryParameter.sortingArray[v] 133 | } 134 | func getResolutionIndex() -> Double { 135 | if let index = QueryParameter.resolutionArray.firstIndex(of: self.resolutions) { 136 | return Double(index) 137 | }else{ 138 | return Double(0) 139 | } 140 | } 141 | func setResolution(v:Int) { 142 | self.resolutions = QueryParameter.resolutionArray[v] 143 | } 144 | 145 | func getRatioIndex() -> Double { 146 | if let index = QueryParameter.ratioArray.firstIndex(of: self.ratios) { 147 | return Double(index) 148 | }else{ 149 | return Double(0) 150 | } 151 | } 152 | func setRatio(v:Int) { 153 | self.resolutions = QueryParameter.resolutionArray[v] 154 | } 155 | func random() { 156 | self.seed = getRandomStringWithNum(num: 6) 157 | } 158 | 159 | private func getRandomStringWithNum(num:Int) -> String { 160 | var string = "" 161 | var i = 0 162 | while i Bool { 13 | return lhs.id == rhs.id 14 | } 15 | let id: String 16 | let url: URL 17 | let shortUrl: URL 18 | let views: Int 19 | let favorites: Int 20 | let source: String 21 | let purity: String 22 | let category: String 23 | let dimensionX: Int 24 | let dimensionY: Int 25 | let resolution: String 26 | let ratio: String 27 | let fileSize: Int 28 | let fileType: String 29 | let createdAt: String 30 | let colors: [String] 31 | let path: URL 32 | struct Thumbs: Codable,Hashable { 33 | let large: URL 34 | let original: URL 35 | let small: URL 36 | } 37 | let thumbs: Thumbs 38 | private enum CodingKeys: String, CodingKey { 39 | case id 40 | case url 41 | case shortUrl = "short_url" 42 | case views 43 | case favorites 44 | case source 45 | case purity 46 | case category 47 | case dimensionX = "dimension_x" 48 | case dimensionY = "dimension_y" 49 | case resolution 50 | case ratio 51 | case fileSize = "file_size" 52 | case fileType = "file_type" 53 | case createdAt = "created_at" 54 | case colors 55 | case path 56 | case thumbs 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /IHaven/Resource/WallHaven/WallHavenImageRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WallHavenImageRepository.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/15. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | 13 | private let QueryAPI = "https://wallhaven.cc/api/v1/search" 14 | private let InfoApi = "https://wallhaven.cc/api/v1/w/" 15 | class WallHavenImageRepository { 16 | static let shared = WallHavenImageRepository() 17 | private init() {} 18 | 19 | @discardableResult 20 | func query(parameters:Parameters?,callback:@escaping (Result) -> Void) -> DataRequest { 21 | AF.request(QueryAPI, 22 | method: .get, 23 | parameters: parameters, 24 | headers: [] 25 | // ,requestModifier: {$0.timeoutInterval = 15} 26 | ) 27 | .responseData { response in 28 | switch response.result{ 29 | case let .success(data): 30 | print(data) 31 | let queryResponse:Result = self.HandleData(data) 32 | callback(queryResponse) 33 | case let .failure(error): callback(.failure(error)) 34 | } 35 | 36 | } 37 | } 38 | 39 | //解析Response 为目标类型 40 | func HandleData(_ reponse:Data) -> Result { 41 | 42 | do { 43 | //TODO useless need remove 44 | _ = try JSONDecoder().decode(T.self, from: reponse) 45 | } 46 | catch { 47 | print(error) 48 | } 49 | guard let resultData = try? JSONDecoder().decode(T.self, from: reponse)else{ 50 | let error = NSError(domain: "WallHavenDataError", code: 0, userInfo: [NSLocalizedDescriptionKey : "解析数据异常"]) 51 | return .failure(error) 52 | } 53 | return .success(resultData) 54 | } 55 | func downloadImage(url:URL,procressCallback:@escaping (Progress)-> Void,callback:@escaping () -> Void) { 56 | let destination = DownloadRequest.suggestedDownloadDestination(for: .downloadsDirectory) 57 | print(url.description) 58 | _ = AF.download(url.description, to: destination).downloadProgress { progress in 59 | procressCallback(progress) 60 | }.response { response in 61 | print(response.description) 62 | callback() 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /IHaven/Resource/WallHaven/WallHavenMeta.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WallHavenMeta.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/17. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | struct WallHavenMeta: Codable { 11 | let currentPage: Int? 12 | let lastPage: Int? 13 | let perPage: String? 14 | let total: Int? 15 | let query: String? 16 | let seed: String? 17 | private enum CodingKeys: String, CodingKey { 18 | case currentPage = "current_page" 19 | case lastPage = "last_page" 20 | case perPage = "per_page" 21 | case total 22 | case query 23 | case seed 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IHaven/Struct/DownloadState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DownloadState.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/7/31. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | // 下载状态 不包括失败 10 | enum DownloadState { 11 | case Nope // 未下载 12 | case Doing // 下载中 13 | case Done // 下载完成 14 | } 15 | -------------------------------------------------------------------------------- /IHaven/Struct/IHavenContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IHavenContext.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/23. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | class IHavenContext: ObservableObject { 11 | @Published var currentState: ViewState = .Main 12 | } 13 | -------------------------------------------------------------------------------- /IHaven/Struct/ImageState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageState.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/21. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | //图像加载状态 10 | enum ImageState { 11 | case Error //异常 12 | case Success //成功 13 | case None //非结果状态 14 | } 15 | -------------------------------------------------------------------------------- /IHaven/Struct/ViewState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewState.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/13. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | enum ViewState { 9 | case Main //主界面 10 | case Filter //查询条件配置界面 11 | case Query //查询界面 @Deprecated 12 | } 13 | -------------------------------------------------------------------------------- /IHaven/Views/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/IHaven/Views/.DS_Store -------------------------------------------------------------------------------- /IHaven/Views/About/AboutContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/22. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct AboutContentView: View { 12 | var body: some View { 13 | VStack(alignment: .center, spacing: 5) { 14 | Image("AppImage").resizable().scaledToFit().frame(height: 60) 15 | Text("IHaven").font(.headline).fontWeight(.regular) 16 | Text("Version 0.0.1(beta)") 17 | .fontWeight(.ultraLight).font(.system(size: 12)) 18 | Text("Copyright © 2020-2020 Laxse").font(.system(size: 10)) 19 | .fontWeight(.ultraLight) 20 | }.frame( maxWidth: .infinity, maxHeight: .infinity) 21 | } 22 | } 23 | 24 | struct AboutContentView_Previews: PreviewProvider { 25 | static var previews: some View { 26 | AboutContentView() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /IHaven/Views/About/AboutWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutViewController.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/22. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | import SwiftUI 12 | class AboutWindowController: NSWindowController { 13 | init() { 14 | let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 280, height: 160), styleMask: [.closable,.titled], backing: .buffered, defer: false) 15 | window.setFrameAutosaveName("About IHaven") 16 | window.center() 17 | window.contentView = NSHostingView(rootView: AboutContentView()) 18 | super.init(window: window) 19 | } 20 | override func loadWindow() { 21 | } 22 | required init?(coder: NSCoder) { 23 | fatalError("init(coder:) has not been implemented") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/Advance/AdvanceContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdvanceContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/7/22. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct AdvanceContentView: View { 12 | var body: some View { 13 | ZStack { 14 | // VStack { 15 | // HStack(alignment: .center) { 16 | // Rectangle().frame(width: 300, height: 300).background(Color.green) 17 | // Spacer() 18 | // } 19 | // Spacer() 20 | // HStack { 21 | // Spacer() 22 | // Rectangle().frame(width: 300, height: 300).background(Color.yellow) 23 | // } 24 | // } 25 | Rectangle().frame(width: 300, height: 300).background(Color.green) 26 | }.frame(maxWidth: .infinity, maxHeight: .infinity) 27 | } 28 | } 29 | 30 | struct AdvanceContentView_Previews: PreviewProvider { 31 | static var previews: some View { 32 | AdvanceContentView() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/Design/DesignContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DesignContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/7/22. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DesignContentView: View { 12 | var body: some View { 13 | 14 | VStack { 15 | Text("Hello, World! DesignContentView") 16 | }.frame(maxWidth: .infinity, maxHeight: .infinity) 17 | } 18 | } 19 | 20 | struct DesignContentView_Previews: PreviewProvider { 21 | static var previews: some View { 22 | DesignContentView() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/DesktopContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/14. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | 10 | import SwiftUI 11 | 12 | struct DesktopContentView: View { 13 | var body: some View { 14 | NavigationView { 15 | List(content: { 16 | NavigationLink(destination: LatestContentView() ) { 17 | Label("Latest", systemImage:"timer") 18 | }.frame(height: 28) 19 | NavigationLink(destination: HotContentView() ) { 20 | Label("Hot", systemImage:"flame") 21 | }.frame(height: 28) 22 | NavigationLink(destination: ToplistContentView()) { 23 | Label("Toplist", systemImage:"rosette") 24 | }.frame(height: 28) 25 | NavigationLink(destination: RandomContentView()) { 26 | Label("Random", systemImage:"arrow.branch") 27 | }.frame(height: 28) 28 | NavigationLink(destination: TagsContentView()) { 29 | Label("Tags", systemImage:"tag") 30 | }.frame(height: 28) 31 | NavigationLink(destination: DesignContentView()) { 32 | Label("Design", systemImage:"wand.and.stars") 33 | }.frame(height: 28) 34 | Divider() 35 | NavigationLink(destination: AdvanceContentView()) { 36 | Label("Advance", systemImage:"hammer") 37 | }.frame(height: 28) 38 | 39 | }) 40 | .listStyle(SidebarListStyle()) 41 | .frame(minWidth:200, maxWidth: .infinity, maxHeight: .infinity) 42 | .ignoresSafeArea(edges: .all) 43 | .onAppear(perform: { 44 | 45 | }) 46 | } 47 | 48 | } 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/DesktopMainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DesktopMain.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/11/14. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DesktopMainView: View { 12 | @State var search = "" 13 | var body: some View{ 14 | SearchBar(search: self.$search) 15 | .padding() 16 | VStack{ 17 | Spacer() 18 | Text("搜索:\(self.search)") 19 | Spacer() 20 | } 21 | Spacer() 22 | } 23 | } 24 | 25 | struct DesktopMain_Previews: PreviewProvider { 26 | static var previews: some View { 27 | DesktopMainView() 28 | } 29 | } 30 | 31 | struct SearchBar:View{ 32 | @Binding var search:String 33 | var body: some View{ 34 | HStack(spacing: 12){ 35 | HStack(spacing: 15){ 36 | Image(systemName: "magnifyingglass") 37 | .foregroundColor(.gray) 38 | 39 | TextField("Search", text: $search) 40 | .textFieldStyle(PlainTextFieldStyle()) 41 | } 42 | .padding(.vertical,10) 43 | .padding(.horizontal) 44 | .background(Color.white) 45 | .cornerRadius(10) 46 | 47 | Button(action: {}, label: { 48 | Image(systemName: "line.horizontal.3.decrease.circle") 49 | .foregroundColor(.black) 50 | .padding(10) 51 | .background(Color.white) 52 | .cornerRadius(10) 53 | .shadow(color: Color.black.opacity(0.1), radius: 5, x: 5, y: 5) 54 | .shadow(color: Color.black.opacity(0.1), radius: 5, x: -5, y: -5) 55 | }) 56 | .buttonStyle(PlainButtonStyle()) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/DesktopWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdvanceDesktopWindowController.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/7/17. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | import SwiftUI 12 | class DesktopWindowController: NSWindowController{ 13 | 14 | init() { 15 | 16 | let iHavenDesktop = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 1000, height: 640), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) 17 | iHavenDesktop.center() 18 | iHavenDesktop.isOpaque = false 19 | iHavenDesktop.titlebarAppearsTransparent = true 20 | iHavenDesktop.titleVisibility = .hidden 21 | //实现自定义标题栏 22 | let iHavenToolbar = NSToolbar() 23 | iHavenToolbar.showsBaselineSeparator = false 24 | iHavenDesktop.toolbar = iHavenToolbar 25 | iHavenDesktop.contentView = NSHostingView(rootView: DesktopContentView() 26 | .environmentObject(AppDelegate.shared!.iHavenContext) 27 | .edgesIgnoringSafeArea(.all) 28 | .frame(minWidth: 1000, minHeight: 640) 29 | .frame(maxWidth: .infinity,maxHeight:.infinity)) 30 | 31 | // 窗口关闭通知,隐藏Dock的图标,程序转变为Anent //暂不隐藏 32 | NotificationCenter.default.addObserver(forName: NSWindow.willCloseNotification, object: iHavenDesktop, queue: OperationQueue.main, using: { note in 33 | NSApp.setActivationPolicy(.accessory) 34 | }) 35 | super.init(window: iHavenDesktop) 36 | } 37 | 38 | required init?(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/Hot/HotContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HotContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2021/9/7. 6 | // Copyright © 2021 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | struct HotContentView: View { 11 | var repo = DImageRepository(query: QueryParameter(sorting: "hot")) 12 | var body: some View { 13 | QueryParameterContentView(repo: repo,message: "Our most popular wallpapers right now!",themeColor: Color.green) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/Latest/LatestContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LatestContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/7/22. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SDWebImageSwiftUI 11 | 12 | struct LatestContentView: View { 13 | var repo = DImageRepository() 14 | 15 | var body: some View { 16 | QueryParameterContentView(repo:repo,message: "The latest wallpapers uploaded by our awesome community!",themeColor: Color.green) 17 | } 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/Random/RandomContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/7/22. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct RandomContentView: View { 12 | var repo = DImageRepository(query: QueryParameter(sorting: "random")) 13 | var body: some View { 14 | QueryParameterContentView(repo: repo,message: "Try your luck!",themeColor: Color.green) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/Tags/TagsContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UploadContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/7/22. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct TagsContentView: View { 12 | var body: some View { 13 | 14 | VStack { 15 | Text("Hello, World! TagsContentView") 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/Toplist/ToplistContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToplistContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/7/22. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ToplistContentView: View { 12 | var repo = DImageRepository(query: QueryParameter(sorting: "toplist")) 13 | var body: some View { 14 | QueryParameterContentView(repo: repo,message: "Here are the most popular walls uploaded within the last month",themeColor: Color.green) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/Widget/DImageRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DImageRepository.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/8/18. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 适用于Desktop版的图片仓库 8 | 9 | import Foundation 10 | class DImageRepository: ObservableObject { 11 | @Published var images:[WallHavenImage] = [] 12 | @Published var query:QueryParameter = QueryParameter() 13 | var loaded = false 14 | 15 | init() { 16 | } 17 | 18 | init(query: QueryParameter) { 19 | self.query = query; 20 | } 21 | 22 | func isLoaded() -> Bool { 23 | return self.loaded; 24 | } 25 | func clean() { 26 | self.images.removeAll() 27 | } 28 | 29 | func reset() -> Void { 30 | query.page = 1; 31 | self.load(succCallBack: { 32 | //成功 33 | }) { 34 | //失败 35 | } 36 | } 37 | 38 | func jump(to: Int, succCallBack: @escaping () -> Swift.Void,errorCallBack: @escaping () -> Swift.Void) { 39 | query.page = to 40 | self.load(succCallBack: succCallBack, errorCallBack: errorCallBack) 41 | } 42 | 43 | func load(succCallBack: @escaping () -> Swift.Void,errorCallBack: @escaping () -> Swift.Void) { 44 | WallHavenImageRepository.shared.query(parameters: query.toDictionary()) { result in 45 | switch result{ 46 | case let .success(data): 47 | self.images.removeAll() 48 | self.images.append(contentsOf: data.data) 49 | self.loaded = true 50 | succCallBack() 51 | case let .failure(error): 52 | print(error.localizedDescription) 53 | errorCallBack() 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /IHaven/Views/Desktop/Widget/QueryParameterContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryParameterContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2021/9/2. 6 | // Copyright © 2021 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SDWebImageSwiftUI 11 | struct QueryParameterContentView: View { 12 | @ObservedObject var imageRepository: DImageRepository 13 | @State var focus: Bool = false 14 | @State var image: WallHavenImage? 15 | var message: String 16 | var themeColor: Color 17 | init(repo:DImageRepository,message:String,themeColor:Color) { 18 | self.imageRepository = repo 19 | self.message = message 20 | self.themeColor = themeColor 21 | if(!repo.isLoaded()){ 22 | repo.load { 23 | print("loaded") 24 | } errorCallBack: { 25 | 26 | } 27 | } 28 | } 29 | var body: some View { 30 | 31 | ZStack(content: { 32 | ScrollView{ 33 | HStack{ 34 | Text(message) 35 | .fontWeight(.ultraLight) 36 | .foregroundColor(themeColor) 37 | .multilineTextAlignment(.leading) 38 | .padding(.top, 10) 39 | .padding(.leading, 20) 40 | Spacer() 41 | }.frame(minHeight: 30, idealHeight: 30, maxHeight: 30) 42 | 43 | 44 | LazyVGrid(columns: [ 45 | GridItem(.adaptive(minimum:240,maximum:240), spacing: 15) 46 | ], spacing: 15){ 47 | ForEach(self.imageRepository.images, id: \.self) { platform in 48 | ImagePanel(image: platform,imageWidth: 240,imageHeight: 160) {_image in 49 | self.image = _image 50 | focus = true 51 | }; 52 | 53 | 54 | } 55 | } 56 | .padding(.horizontal, 20) 57 | .padding(.top, 0) 58 | .padding(.bottom, 20) 59 | }.edgesIgnoringSafeArea(.all) 60 | .blur(radius: blurForBackground(focus: self.focus) ) 61 | HStack(alignment: .center) { 62 | Spacer() 63 | Pagination(count: 5000, size: 24, active: self.imageRepository.query.page,changeFunc: { page in 64 | self.imageRepository.jump(to: page, succCallBack: { 65 | // success 66 | }) { 67 | // error 68 | } 69 | }) 70 | .padding(.trailing, 10) 71 | .frame(maxHeight: .infinity) 72 | }.edgesIgnoringSafeArea(.all) 73 | if focus == true { 74 | HStack(alignment: .center, content: { 75 | VStack(alignment: .leading, spacing: 0,content: { 76 | Text(self.image!.resolution) 77 | .font(.title) 78 | .fontWeight(.ultraLight) 79 | .padding(.vertical, 20.0) 80 | .frame(width: 200, height: 50, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) 81 | HStack(alignment: .center, spacing: 0.0, content: { 82 | 83 | ForEach(self.image!.colors, id: \.self) { i in 84 | Rectangle().fill(Color(hex: i)) 85 | .frame(width: 30, height: 10, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) 86 | } 87 | }).frame(width: 200, height: 30, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) 88 | Divider() 89 | Text("TAGS") 90 | .font(.callout) 91 | .fontWeight(.ultraLight) 92 | .multilineTextAlignment(.leading) 93 | .padding(.top,20) 94 | .padding(.leading, /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/) 95 | .padding(.bottom,10) 96 | HStack(content: { 97 | Spacer() 98 | VStack(alignment: .trailing, content: { 99 | Text("Uploader") 100 | Text("Category") 101 | Text("Purity") 102 | Text("Size") 103 | Text("Favorites") 104 | Text("Views") 105 | }) 106 | Spacer() 107 | VStack(alignment: .leading, content: { 108 | Text(self.image!.id) 109 | Text(self.image!.category).foregroundColor(Color.green) 110 | Text(self.image!.purity) 111 | Text(String(self.image!.fileSize)) 112 | Text(String(self.image!.favorites)).foregroundColor(Color.blue) 113 | 114 | 115 | Text(String(self.image!.views)) 116 | }) 117 | Spacer() 118 | }) 119 | 120 | Divider() 121 | Text("PROPERTIES") 122 | .font(.callout) 123 | .fontWeight(.ultraLight) 124 | .multilineTextAlignment(.leading) 125 | .padding(.top,20) 126 | .padding(.leading, /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/) 127 | .padding(.bottom,10) 128 | 129 | 130 | Spacer() 131 | Text("All images remain property of their original owners.") 132 | .fontWeight(.light) 133 | .foregroundColor(Color.gray) 134 | .multilineTextAlignment(.center) 135 | .padding() 136 | .frame(width: 200) 137 | .border(Color.gray) 138 | Spacer() 139 | }) 140 | .frame(minWidth: 200,idealWidth: 200,maxWidth: 200,maxHeight: .infinity, alignment: .center) 141 | .background(Color.black) 142 | VStack(alignment: .center, content: { 143 | HStack{ 144 | Spacer() 145 | Image(systemName: "xmark.circle") 146 | .resizable(resizingMode: .stretch) 147 | .foregroundColor(Color.red) 148 | .frame(width: 15.0, height: 15.0).onTapGesture { 149 | self.focus = false 150 | } 151 | }.padding([.top,.trailing], 10) 152 | Spacer() 153 | 154 | WebImage(url: self.image?.path).resizable().scaledToFit().clipped() 155 | Spacer() 156 | }) 157 | Spacer() 158 | }) 159 | .background(Color.black) 160 | .opacity(0.9) 161 | 162 | 163 | .edgesIgnoringSafeArea(.all) 164 | } 165 | 166 | }).frame(maxWidth: .infinity, maxHeight: .infinity) 167 | 168 | 169 | } 170 | 171 | func blurForBackground(focus: Bool) -> CGFloat { 172 | if focus { 173 | return 3.0 174 | }else{ 175 | return 0 176 | } 177 | } 178 | //显示png标签 179 | func typeTextFor(image:WallHavenImage?) -> Bool { 180 | if(image!.fileType.hasSuffix("png")){ 181 | return true 182 | }else{ 183 | return false 184 | } 185 | } 186 | } 187 | 188 | 189 | -------------------------------------------------------------------------------- /IHaven/Views/Filter/CheckableButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CheckableButton.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/28. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | // 可点击按钮 拥有点击状态,可设置选中/未选中状态的前景色和背景色 9 | 10 | import SwiftUI 11 | 12 | struct CheckableButton: View { 13 | var lable:String 14 | var unclickFontColor:Color 15 | var clickFontColor:Color 16 | var unclickBackColor:Color 17 | var clickBackColor:Color 18 | var clickCallBack:(Bool)-> Void 19 | @State var checked:Bool 20 | 21 | var body: some View{ 22 | Button(action: { 23 | self.checked.toggle() 24 | self.clickCallBack(self.checked) 25 | }) { 26 | Text(lable) 27 | } 28 | .buttonStyle(checked ? CheckedButtonStyle(fontColor: clickFontColor) : CheckedButtonStyle(fontColor: unclickFontColor)) 29 | .frame(width: 80, height: 30, alignment: .center) 30 | .background(checked ? clickBackColor : unclickBackColor) 31 | .cornerRadius(2.0) 32 | } 33 | } 34 | struct CheckedButtonStyle: ButtonStyle{ 35 | var fontColor:Color 36 | func makeBody(configuration: CheckedButtonStyle.Configuration) -> some View { 37 | configuration.label.foregroundColor(fontColor) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /IHaven/Views/Filter/FilterContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/13. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct FilterContentView: View { 12 | @ObservedObject var imageRepository: ImageRepository = ImageRepository.shared 13 | @ViewBuilder 14 | var body: some View { 15 | VStack(){ 16 | FilterHeader() 17 | QueryFilter(query: imageRepository.query).padding(.top,0) 18 | Spacer() 19 | } 20 | } 21 | 22 | } 23 | struct QueryFilter: View { 24 | @State var query:QueryParameter 25 | var body: some View{ 26 | List(){ 27 | CategorySection(query: $query) 28 | 29 | PuritySection(query: $query) 30 | 31 | ResolutionSelection(query: $query, select: query.getResolutionIndex(), label: query.resolutions) 32 | 33 | RatioSelection(query: $query, select: query.getRatioIndex(), label: query.ratios) 34 | 35 | ColorSection(query: $query, currentLabel: query.color) 36 | 37 | SortSelection(query: $query, select: query.getSortIndex(), label: query.sorting) 38 | }.listStyle(SidebarListStyle()) 39 | 40 | } 41 | } 42 | struct SortSelection:View { 43 | @Binding var query:QueryParameter 44 | @State var select: Double 45 | @State var label:String 46 | var body: some View{ 47 | Section(header:FilterPartLabel(label: "Sort")) { 48 | Text(label) 49 | Slider(value: Binding(get: { 50 | self.select 51 | }, set: { value in 52 | self.select = value 53 | self.label = QueryParameter.sortingArray[Int(value)] 54 | self.query.setSorting(v: Int(value)) 55 | }), in: 0...Double(QueryParameter.sortingArray.count-1),step: 1) 56 | } 57 | } 58 | } 59 | 60 | struct RatioSelection:View { 61 | @Binding var query:QueryParameter 62 | @State var select: Double 63 | @State var label:String 64 | var body: some View{ 65 | Section(header:FilterPartLabel(label: "Ratio")) { 66 | Text(label) 67 | Slider(value: Binding(get: { 68 | self.select 69 | }, set: { value in 70 | self.select = value 71 | self.label = QueryParameter.ratioArray[Int(value)] 72 | self.query.setRatio(v: Int(value)) 73 | }), in: 0...Double(QueryParameter.ratioArray.count-1),step: 1) 74 | } 75 | } 76 | } 77 | 78 | struct ResolutionSelection:View { 79 | @Binding var query:QueryParameter 80 | @State var select: Double 81 | @State var label:String 82 | 83 | var body: some View{ 84 | Section(header:FilterPartLabel(label: "Resolution")) { 85 | Text(label) 86 | Slider(value: Binding(get: { 87 | self.select 88 | }, set: { value in 89 | self.select = value 90 | self.label = QueryParameter.resolutionArray[Int(value)] 91 | self.query.setResolution(v: Int(value)) 92 | }), in: 0...Double(QueryParameter.resolutionArray.count-1),step: 1) 93 | } 94 | } 95 | 96 | } 97 | //分类条件 98 | struct CategorySection: View { 99 | @Binding var query:QueryParameter 100 | var body: some View{ 101 | Section(header:FilterPartLabel(label: "Category")) { 102 | Group{ 103 | HStack(alignment: .bottom, spacing: 5) { 104 | CheckableButton(lable: "General", unclickFontColor: Color.init(hex: "AAAAAA"), clickFontColor: Color.white, unclickBackColor: Color.init(hex: "5E5E5E"), clickBackColor: Color.init(hex:"777777"), clickCallBack: {boolean in 105 | self.query.setCategory(i: 0, v: boolean) 106 | }, checked: query.getCategory(i: 0)) 107 | CheckableButton(lable: "Anime", unclickFontColor: Color.init(hex: "AAAAAA"), clickFontColor: Color.white, unclickBackColor: Color.init(hex:"777777"), clickBackColor: Color.init(hex: "5E5E5E"), clickCallBack: {boolean in 108 | self.query.setCategory(i: 1, v: boolean) 109 | }, checked: query.getCategory(i: 1)) 110 | CheckableButton(lable: "People", unclickFontColor: Color.init(hex: "AAAAAA"), clickFontColor: Color.white, unclickBackColor: Color.init(hex:"777777"), clickBackColor: Color.init(hex: "5E5E5E"), clickCallBack: {boolean in 111 | self.query.setCategory(i: 2, v: boolean) 112 | }, checked: query.getCategory(i: 2)) 113 | }.padding(.horizontal, 5).padding(.vertical, 5).background(Color.black) 114 | }.padding(.top, 10) 115 | } 116 | } 117 | } 118 | 119 | //图片污力条件 120 | struct PuritySection:View { 121 | @Binding var query:QueryParameter 122 | var body: some View{ 123 | Section(header:FilterPartLabel(label: "Purity")) { 124 | Group{ 125 | HStack(alignment: .bottom, spacing: 5) { 126 | CheckableButton(lable: "SFW", unclickFontColor: Color.init(hex: "AAAAAA"), clickFontColor: Color.init(hex: "99FF99"), unclickBackColor: Color.init(hex: "5E5E5E"), clickBackColor: Color.init(hex: "447744"), clickCallBack: {boolean in 127 | self.query.setPurity(i: 0, v: boolean) 128 | }, checked: self.query.getPurity(i: 0)) 129 | // CheckableButton(lable: "Sketchy", unclickFontColor: Color.init(hex: "AAAAAA"), clickFontColor: Color.init(hex: "FFFF99"), unclickBackColor: Color.init(hex: "5E5E5E"), clickBackColor: Color.init(hex: "777744"), clickCallBack: {boolean in 130 | // self.query.setPurity(i: 1, v: boolean) 131 | // }, checked: self.query.getPurity(i: 1)) 132 | // CheckableButton(lable: "NSFW", unclickFontColor: Color.init(hex: "AAAAAA"), clickFontColor: Color.init(hex: "FF9999"), unclickBackColor: Color.init(hex: "5E5E5E"), clickBackColor: Color.init(hex: "774444"), clickCallBack: {boolean in 133 | // self.query.setPurity(i: 2, v: boolean) 134 | // }, checked: self.query.getPurity(i: 2)) 135 | }.padding(.horizontal, 5).padding(.vertical, 5).background(Color.black) 136 | }.padding(.top, 10) 137 | 138 | } 139 | } 140 | } 141 | 142 | 143 | struct ColorSection:View { 144 | @Binding var query:QueryParameter 145 | @State var currentLabel:String 146 | var body: some View{ 147 | Section(header:FilterPartLabel(label: "Color")) { 148 | VStack(alignment: .leading, spacing: 5, content: { 149 | HStack{ 150 | Text(currentLabel) 151 | Spacer() 152 | } 153 | //TODO 改成等宽字体 154 | HStack(spacing: 5){ 155 | ColorText(currentLabel: $currentLabel, query: $query,label: "660000") 156 | ColorText(currentLabel: $currentLabel, query: $query,label: "990000") 157 | ColorText(currentLabel: $currentLabel, query: $query,label: "CC0000") 158 | ColorText(currentLabel: $currentLabel, query: $query,label: "CC3333") 159 | ColorText(currentLabel: $currentLabel, query: $query,label: "EA4C88") 160 | ColorText(currentLabel: $currentLabel, query: $query,label: "993399") 161 | } 162 | HStack(spacing: 5){ 163 | ColorText(currentLabel: $currentLabel, query: $query, label: "663399") 164 | ColorText(currentLabel: $currentLabel, query: $query, label: "333399") 165 | ColorText(currentLabel: $currentLabel, query: $query, label: "0066CC") 166 | ColorText(currentLabel: $currentLabel, query: $query, label: "0099CC") 167 | ColorText(currentLabel: $currentLabel, query: $query, label: "66CCCC") 168 | ColorText(currentLabel: $currentLabel, query: $query, label: "77CC33") 169 | } 170 | HStack(spacing: 5){ 171 | ColorText(currentLabel: $currentLabel, query: $query, label: "669900") 172 | ColorText(currentLabel: $currentLabel, query: $query, label: "336600") 173 | ColorText(currentLabel: $currentLabel, query: $query, label: "666600") 174 | ColorText(currentLabel: $currentLabel, query: $query, label: "999900") 175 | ColorText(currentLabel: $currentLabel, query: $query, label: "CCCC33") 176 | ColorText(currentLabel: $currentLabel, query: $query, label: "FFFF00") 177 | } 178 | HStack(spacing: 5){ 179 | ColorText(currentLabel: $currentLabel, query: $query,label: "FFCC33") 180 | ColorText(currentLabel: $currentLabel, query: $query,label: "FF9900") 181 | ColorText(currentLabel: $currentLabel, query: $query,label: "FF6600") 182 | ColorText(currentLabel: $currentLabel, query: $query,label: "CC6633") 183 | ColorText(currentLabel: $currentLabel, query: $query,label: "996633") 184 | ColorText(currentLabel: $currentLabel, query: $query,label: "663300") 185 | } 186 | HStack(spacing: 5){ 187 | ColorText(currentLabel: $currentLabel, query: $query, label: "000000") 188 | ColorText(currentLabel: $currentLabel, query: $query, label: "999999") 189 | ColorText(currentLabel: $currentLabel, query: $query, label: "CCCCCC") 190 | ColorText(currentLabel: $currentLabel, query: $query, label: "FFFFFF") 191 | ColorText(currentLabel: $currentLabel, query: $query, label: "424153") 192 | ColorText(currentLabel: $currentLabel, query: $query, label: "NULL") 193 | } 194 | }) 195 | } 196 | } 197 | } 198 | 199 | 200 | 201 | //section 标题 202 | struct FilterPartLabel: View { 203 | var label:String 204 | 205 | var body: some View{ 206 | Text(label).font(.headline).fontWeight(.ultraLight).foregroundColor(Color.purple).multilineTextAlignment(.leading).shadow(color: .black, radius: 2, x: 0.1, y: 0.1) 207 | .edgesIgnoringSafeArea(.all) 208 | } 209 | } 210 | 211 | //颜色过滤配置 212 | struct ColorText:View { 213 | @Binding var currentLabel:String 214 | @Binding var query:QueryParameter 215 | var label:String 216 | var body: some View{ 217 | Text(label).font(.system(size: 8)).frame(width: 40, height: 20, alignment: .bottomTrailing).padding(.all, 5).background(Color.init(hex: label)).onTapGesture { 218 | self.query.setColor(color: self.label) 219 | self.currentLabel = self.label 220 | } 221 | } 222 | } 223 | 224 | -------------------------------------------------------------------------------- /IHaven/Views/Filter/FilterHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterHeader.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/13. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | struct FilterHeader: View { 11 | var body: some View { 12 | HStack(alignment: .center){ 13 | FilterBackBtn() 14 | Spacer() 15 | } 16 | .padding(.vertical,7) 17 | .padding(.horizontal,6) 18 | .frame(height: 40) 19 | .background(Color.black) 20 | } 21 | } 22 | /** 23 | * 查询条件按钮 24 | */ 25 | struct FilterBackBtn: View { 26 | static var FilterBtnWidth:CGFloat = 16.0 27 | @EnvironmentObject var iHavenContext: IHavenContext 28 | var body : some View{ 29 | Button(action: { 30 | withAnimation(.easeOut(duration: 0.3)) { 31 | self.iHavenContext.currentState = .Main 32 | ImageRepository.shared.clean() 33 | ImageRepository.shared.query.page = 1 34 | ImageRepository.shared.load(succCallBack: {}) {} 35 | } 36 | }){ 37 | Image(nsImage: NSImage(named: "BackBtn")!).resizable() 38 | } 39 | .frame(width: FilterBackBtn.FilterBtnWidth * 2, height: FilterBackBtn.FilterBtnWidth) 40 | .buttonStyle(PlainButtonStyle()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /IHaven/Views/Main/MainContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/13. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SDWebImageSwiftUI 11 | 12 | struct MainContentView: View { 13 | 14 | @ObservedObject var imageRepository: ImageRepository = ImageRepository.shared 15 | @ViewBuilder 16 | var body: some View { 17 | VStack(spacing: 0){ 18 | MainHeader() 19 | List() { 20 | ForEach(imageRepository.images) { imageLine in 21 | HStack(spacing: 10){ 22 | ImagePanel(image: imageLine.left,imageWidth: 175).frame(maxWidth: .infinity) 23 | ImagePanel(image: imageLine.right,imageWidth: 175).frame(maxWidth: .infinity) 24 | }.onAppear { 25 | self.imageRepository.lastImageLineAppear(currentItem: imageLine) 26 | } 27 | } 28 | HStack{ 29 | ActivityIndicator(.constant(imageRepository.loading)) 30 | }.frame(maxWidth:.infinity) 31 | } 32 | 33 | MainFooter() 34 | }.frame(maxHeight: .infinity) 35 | } 36 | 37 | } 38 | 39 | extension MainContentView{ 40 | 41 | } 42 | -------------------------------------------------------------------------------- /IHaven/Views/Main/MainFooter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Footer.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/13. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct MainFooter: View { 12 | var body: some View { 13 | HStack(alignment: .center) { 14 | Text("Made With").foregroundColor(Color.purple) 15 | Text("❤").foregroundColor(Color.red) 16 | Text("By Laxse ").foregroundColor(Color.purple) 17 | Spacer() 18 | Button(action: { 19 | AppDelegate.shared?.showDesktopWindow() 20 | }) { 21 | Text("高级模式") 22 | } 23 | SettingBtn() 24 | }.frame(height: 40).padding(.horizontal, 10) 25 | } 26 | } 27 | 28 | /** 29 | * 查询条件按钮 30 | */ 31 | struct SettingBtn: View { 32 | static var SettingBtnWidth:CGFloat = 25.0 33 | var body : some View{ 34 | MenuButton(label: Image("PreferencesButtonIcon") 35 | .frame(width: SettingBtn.SettingBtnWidth, height: SettingBtn.SettingBtnWidth)) { 36 | Button(action: { 37 | AppDelegate.shared?.showAboutWindow() 38 | }) { 39 | Text("About IHaven") 40 | } 41 | Button(action: { 42 | NSWorkspace.shared.open(URL(string: "https://github.com/Laxse/IHaven")!) 43 | }) { 44 | Text("Send Feedback...") 45 | } 46 | Button(action: { 47 | // 哭了哭了 48 | }) { 49 | Text("我好卑微啊~") 50 | } 51 | Button(action: { 52 | NSApplication.shared.terminate(self) 53 | }) { 54 | Text("Quit") 55 | } 56 | } 57 | .frame(width: SettingBtn.SettingBtnWidth, height: SettingBtn.SettingBtnWidth) 58 | .menuButtonStyle(BorderlessButtonMenuButtonStyle()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /IHaven/Views/Main/MainHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Header.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/12. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct MainHeader: View { 12 | var body: some View { 13 | HStack(alignment: .center) { 14 | RandomBtn() 15 | Spacer() 16 | Text("IHaven") 17 | .font(.title) 18 | .fontWeight(.ultraLight) 19 | .foregroundColor(Color.purple) 20 | .multilineTextAlignment(.center) 21 | Spacer() 22 | FilterBtn() 23 | } 24 | .padding(.horizontal,10) 25 | .padding(.vertical, 3) 26 | .frame(height: 40) 27 | .background(Color.black.opacity(0.5)) 28 | } 29 | } 30 | /** 31 | * 查询条件按钮 32 | */ 33 | struct RandomBtn: View { 34 | static var RandomBtnWidth:CGFloat = 18.0 35 | var body : some View{ 36 | Button(action: { 37 | ImageRepository.shared.clean(); 38 | ImageRepository.shared.query.random() 39 | ImageRepository.shared.load(succCallBack: {}) {} 40 | }){ 41 | Image(nsImage: NSImage(named: "RandomBtn")!).resizable() 42 | } 43 | .frame(width: RandomBtn.RandomBtnWidth, height: RandomBtn.RandomBtnWidth) 44 | .buttonStyle(PlainButtonStyle()) 45 | } 46 | } 47 | 48 | /** 49 | * 查询按钮 50 | */ 51 | struct FilterBtn: View { 52 | static var FilterBtnWidth:CGFloat = 18.0 53 | @EnvironmentObject var iHavenContext: IHavenContext 54 | var body : some View{ 55 | Button(action: { 56 | withAnimation(.easeOut(duration: 0.3)) { 57 | self.iHavenContext.currentState = .Filter 58 | } 59 | }){ 60 | Image(nsImage: NSImage(named: "FilterBtn")!).resizable() 61 | } 62 | .frame(width: FilterBtn.FilterBtnWidth, height: FilterBtn.FilterBtnWidth) 63 | .buttonStyle(PlainButtonStyle()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /IHaven/Views/Panel/ImageLine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLine.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/21. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ImageLine:Identifiable { 12 | let uuid = UUID() 13 | var left:WallHavenImage? 14 | var right:WallHavenImage? 15 | init(left:WallHavenImage?,right:WallHavenImage?) { 16 | self.left = left 17 | self.right = right 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /IHaven/Views/Panel/ImagePanel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePanel.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/16. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import SDWebImageSwiftUI 11 | 12 | struct ImagePanel: View { 13 | 14 | @State var image: WallHavenImage? 15 | // 图片加载状态 16 | @State var status: ImageState = .None 17 | // 显示图片详情 18 | @State var showHoverDetail = false 19 | // 下载状态 20 | @State var downloadState: DownloadState = .Nope 21 | // 下载进度 22 | @State var downloadProgress: Double = 0.0 23 | 24 | @State var imageWidth: CGFloat = 180 25 | 26 | @State var imageHeight: CGFloat = 120 27 | 28 | @State var clickEvent: ((WallHavenImage) -> Void)? 29 | var body: some View { 30 | ZStack(alignment: .center){ 31 | WebImage(url: image?.thumbs.small) 32 | .onFailure(perform: { (Error) in 33 | DispatchQueue.main.async { 34 | self.image = nil 35 | self.status = .Error 36 | self.showHoverDetail = false 37 | } 38 | 39 | }) 40 | .onSuccess(perform: { _,_,_ in 41 | DispatchQueue.main.async { 42 | self.status = .Success 43 | } 44 | 45 | }) 46 | .placeholder(placeholderFor(status:status)) 47 | .resizable() 48 | .frame(width: imageWidth,height: imageHeight) 49 | .scaledToFill() 50 | .border(colorFor(purity: image?.purity ?? "sfw"),width: 2.0) 51 | .clipped() 52 | .cornerRadius(2.0) 53 | .onHover { state in 54 | withAnimation(.easeIn(duration: 0.3)) { 55 | self.showHoverDetail = state 56 | } 57 | }.onTapGesture { 58 | // image not well 59 | if clickEvent == nil{ 60 | self.downloadImage() 61 | }else{ 62 | clickEvent!(image!) 63 | } 64 | // self.downloadImage() 65 | 66 | } 67 | 68 | //图片上层悬浮信息 69 | if showHoverDetail && (image != nil) { 70 | VStack(alignment: .leading) { 71 | // Text(_msg) 72 | Spacer() 73 | 74 | HStack { 75 | ZStack(){ 76 | HStack{ 77 | Spacer() 78 | Text(resolutionFor(image: image)).foregroundColor(Color.init(hex: "FFFBF0")) 79 | Spacer() 80 | }.frame(maxWidth: .infinity) 81 | HStack(spacing:0){ 82 | Text(image?.favorites.description ?? "0") 83 | Image("StarBtn").resizable().frame(width: 14, height: 14) 84 | Spacer() 85 | if(typeTextFor(image: image)){ 86 | Text("png") 87 | .font(.system(size: 12)) 88 | .padding(.horizontal, 2) 89 | .padding(.top, -3) 90 | .background(Color.init(hex: "DB7C0F")) 91 | .cornerRadius(2) 92 | } 93 | Image("TagBtn").resizable().frame(width: 16, height: 16) 94 | 95 | }.frame(maxWidth: .infinity).frame(height:20).padding(.horizontal, 3) 96 | } 97 | 98 | }.background(LinearGradient(gradient: Gradient(colors: [Color.black.opacity(0.9), Color.black.opacity(0.1)]), startPoint: .bottom, endPoint: .top)) 99 | 100 | } 101 | .border(colorFor(purity: image?.purity ?? "sfw"),width: 2.0) 102 | .frame(width: imageWidth,height: imageHeight) 103 | } 104 | 105 | if downloadState == .Doing { 106 | Color.black.opacity(0.3) 107 | ProgressBar(progress: $downloadProgress) 108 | } 109 | if showHoverDetail && downloadState == .Done { 110 | //TODO 设置壁纸按钮 111 | Button(action: { 112 | self.setImageAsWallPaper() 113 | }){ 114 | VStack(alignment: .center) { 115 | Text("Set").font(.body).fontWeight(.ultraLight).foregroundColor(Color.purple) 116 | Text("WallPaper").font(.body).fontWeight(.ultraLight).foregroundColor(Color.purple) 117 | }.frame(width: 80, height: 80).background(Color.black.opacity(0.7)).cornerRadius(100) 118 | }.buttonStyle(BorderlessButtonStyle()).padding() 119 | } 120 | } 121 | } 122 | 123 | 124 | } 125 | 126 | //状态函数 127 | extension ImagePanel{ 128 | //返回分辨率 129 | func resolutionFor(image:WallHavenImage?) -> String { 130 | return image!.resolution 131 | } 132 | 133 | func placeholderFor(status:ImageState) -> Image { 134 | if(status == .Error){ 135 | return Image.init("ImageErrorIcon") 136 | }else{ 137 | return Image.init("LoadingIcon") 138 | 139 | } 140 | } 141 | //显示png标签 142 | func typeTextFor(image:WallHavenImage?) -> Bool { 143 | if(image!.fileType.hasSuffix("png")){ 144 | return true 145 | }else{ 146 | return false 147 | } 148 | } 149 | 150 | //返回purity边框色 151 | func colorFor(purity:String) -> Color { 152 | if(purity.caseInsensitiveCompare("sfw") == .orderedSame){ 153 | return Color.clear 154 | }else if(purity.caseInsensitiveCompare("sketchy") == .orderedSame){ 155 | return Color.init(hex: "FFC840") 156 | }else if(purity.caseInsensitiveCompare("nsfw") == .orderedSame){ 157 | return Color.init(hex: "FF4030") 158 | } 159 | return Color.clear 160 | } 161 | } 162 | 163 | //事件函数 164 | extension ImagePanel{ 165 | // 下载图片点击事件 166 | func downloadImage() -> Void { 167 | if(self.status != .Success){ 168 | return 169 | } 170 | // check if download 171 | if(self.checkImageExist()){ 172 | self.downloadState = .Done 173 | } 174 | if(self.downloadState == .Nope){ 175 | self.downloadState = .Doing 176 | WallHavenImageRepository.shared.downloadImage(url: self.image!.path, procressCallback: { progress in 177 | self.downloadProgress = progress.fractionCompleted 178 | }, callback: { 179 | self.downloadState = .Done 180 | }) 181 | } 182 | } 183 | 184 | // 设置壁纸点击事件 185 | func setImageAsWallPaper() -> Void { 186 | do { 187 | let imgurl = self.getDownloadImagePath() 188 | let workspace = NSWorkspace.shared 189 | if let screen = NSScreen.main { 190 | try workspace.setDesktopImageURL(imgurl!, for: screen, options: [:]) 191 | } 192 | } catch { 193 | print(error) 194 | } 195 | } 196 | 197 | //校验图片是否存在 198 | func checkImageExist() -> Bool { 199 | if let imagePathComponent = getDownloadImagePath() { 200 | let imagePath = imagePathComponent.path 201 | if FileManager.default.fileExists(atPath: imagePath) { 202 | return true 203 | } else { 204 | return false 205 | } 206 | } else { 207 | return false 208 | } 209 | } 210 | 211 | //返回图片名 212 | func getDownloadImagePath() -> URL? { 213 | let downloadPath = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.downloadsDirectory, .userDomainMask, true)[0] as String) 214 | let imageUrl = image!.path.absoluteString 215 | return downloadPath.appendingPathComponent(imageUrl[imageUrl.lastIndex(of: "/")!.. Swift.Void,errorCallBack: @escaping () -> Swift.Void) { 72 | self.loading = true 73 | WallHavenImageRepository.shared.query(parameters: query.toDictionary()) { result in 74 | switch result{ 75 | case let .success(data): 76 | self.query.page = self.query.page + 1 77 | var imageLines:[ImageLine] = [] 78 | var imageLine = ImageLine(left: nil, right: nil) 79 | data.data.forEach { image in 80 | if(imageLine.left==nil){ 81 | imageLine.left = image 82 | }else if(imageLine.right==nil){ 83 | imageLine.right = image 84 | } 85 | 86 | if(imageLine.left != nil && imageLine.right != nil){ 87 | imageLines.append(imageLine) 88 | imageLine = ImageLine(left: nil, right: nil) 89 | } 90 | } 91 | self.images.append(contentsOf: imageLines) 92 | self.loading = false 93 | succCallBack() 94 | case let .failure(error): 95 | print(error.localizedDescription) 96 | self.loading = false 97 | errorCallBack() 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /IHaven/Views/Panel/LoadingPanel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingPanel.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/25. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct LoadingPanel: View { 12 | @State var animateTrimPath = false 13 | @State var rotateInfinity = false 14 | var body: some View { 15 | ZStack{ 16 | Path{ path in 17 | path.addLines([ 18 | .init(x: 2, y: 1), 19 | .init(x: 1, y: 0), 20 | .init(x: 0, y: 1), 21 | .init(x: 1, y: 2), 22 | .init(x: 3, y: 0), 23 | .init(x: 4, y: 1), 24 | .init(x: 3, y: 2), 25 | .init(x: 2, y: 1), 26 | ]) 27 | } 28 | .trim(from: animateTrimPath ? 1/0.99 : 0, to: animateTrimPath ? 1/0.99 : 1) 29 | .scale(50, anchor: .topLeading) 30 | .stroke(Color.purple,lineWidth: 6) 31 | .offset(x: 100, y: 350) 32 | animation(Animation.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) 33 | .onAppear(){ 34 | self.animateTrimPath.toggle() 35 | } 36 | } 37 | .rotationEffect(.degrees(rotateInfinity ? 0 : -360)) 38 | .scaleEffect(0.3, anchor: .center) 39 | .animation(Animation.easeInOut(duration: 1.5).repeatForever(autoreverses: false)) 40 | .onAppear(){ 41 | self.rotateInfinity.toggle() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /IHaven/Views/Panel/Pagination.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pagination.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/8/5. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct Pagination: View { 12 | @State var count:Int 13 | @State var size: Int 14 | @State var active: Int 15 | var changeFunc:(Int) -> Swift.Void 16 | 17 | var body: some View { 18 | VStack(alignment: .trailing) { 19 | Text("Last").font(Font.system(size: 18)) 20 | .fontWeight(.ultraLight) 21 | .padding() 22 | .onTapGesture { 23 | self.move(distance: -1) 24 | } 25 | Text("#"+String(active)) 26 | .font(.title) 27 | .fontWeight(.ultraLight) 28 | .foregroundColor(Color.purple) 29 | .multilineTextAlignment(.trailing) 30 | .padding() 31 | 32 | Spacer() 33 | Text("Next") 34 | .font(Font.system(size: 18)) 35 | .fontWeight(.ultraLight) 36 | .padding() 37 | .onTapGesture { 38 | self.move(distance: 1) 39 | } 40 | 41 | } 42 | } 43 | } 44 | extension Pagination { 45 | 46 | func move(distance:Int) -> Void { 47 | move(to: self.active + distance) 48 | } 49 | 50 | func move(to: Int) -> Void { 51 | if(count <= 0){ 52 | return 53 | } 54 | 55 | if(to > 0 && to < Int(ceil(Double(count)/Double(size)))){ 56 | // withAnimation(.easeInOut) { 57 | self.active = to 58 | changeFunc(self.active) 59 | // } 60 | 61 | } 62 | } 63 | 64 | } 65 | 66 | struct Pagination_Previews: PreviewProvider { 67 | static var previews: some View { 68 | Pagination(count: 500, size: 25, active: 5, changeFunc: { page in 69 | 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /IHaven/Views/Query/QueryContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryContentView.swift 3 | // IHaven 4 | // 5 | // Created by 梁霄 on 2020/6/14. 6 | // Copyright © 2020 梁霄. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct QueryContentView: View { 12 | @Binding var currentView: ViewState 13 | var body: some View { 14 | VStack{ 15 | QueryBackBtn(currentView: $currentView) 16 | Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) 17 | } 18 | } 19 | } 20 | 21 | /** 22 | * 查询条件按钮 23 | */ 24 | struct QueryBackBtn: View { 25 | static var QueryBtnWidth:CGFloat = 25.0 26 | @Binding var currentView: ViewState 27 | var body : some View{ 28 | Button(action: { 29 | self.currentView = .Main 30 | }){ 31 | Image(nsImage: NSImage(named: "BackButton")!) 32 | } 33 | .frame(width: QueryBackBtn.QueryBtnWidth, height: QueryBackBtn.QueryBtnWidth) 34 | .buttonStyle(PlainButtonStyle()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /IHaven/testdata/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "lm1pyy", 5 | "url": "https://wallhaven.cc/w/lm1pyy", 6 | "short_url": "https://whvn.cc/lm1pyy", 7 | "views": 22, 8 | "favorites": 0, 9 | "source": "", 10 | "purity": "sketchy", 11 | "category": "anime", 12 | "dimension_x": 3840, 13 | "dimension_y": 2160, 14 | "resolution": "3840x2160", 15 | "ratio": "1.78", 16 | "file_size": 5885715, 17 | "file_type": "image/png", 18 | "created_at": "2020-06-19 14:38:45", 19 | "colors": [ 20 | "#424153", 21 | "#999999", 22 | "#cccccc", 23 | "#abbcda", 24 | "#996633" 25 | ], 26 | "path": "https://w.wallhaven.cc/full/lm/wallhaven-lm1pyy.png", 27 | "thumbs": { 28 | "large": "https://th.wallhaven.cc/lg/lm/lm1pyy.jpg", 29 | "original": "https://th.wallhaven.cc/orig/lm/lm1pyy.jpg", 30 | "small": "https://th.wallhaven.cc/small/lm/lm1pyy.jpg" 31 | } 32 | }, 33 | { 34 | "id": "ympoqx", 35 | "url": "https://wallhaven.cc/w/ympoqx", 36 | "short_url": "https://whvn.cc/ympoqx", 37 | "views": 26, 38 | "favorites": 0, 39 | "source": "", 40 | "purity": "sfw", 41 | "category": "general", 42 | "dimension_x": 4032, 43 | "dimension_y": 3024, 44 | "resolution": "4032x3024", 45 | "ratio": "1.33", 46 | "file_size": 2979661, 47 | "file_type": "image/jpeg", 48 | "created_at": "2020-06-19 14:27:31", 49 | "colors": [ 50 | "#cc3333", 51 | "#996633", 52 | "#999999", 53 | "#cccccc", 54 | "#000000" 55 | ], 56 | "path": "https://w.wallhaven.cc/full/ym/wallhaven-ympoqx.jpg", 57 | "thumbs": { 58 | "large": "https://th.wallhaven.cc/lg/ym/ympoqx.jpg", 59 | "original": "https://th.wallhaven.cc/orig/ym/ympoqx.jpg", 60 | "small": "https://th.wallhaven.cc/small/ym/ympoqx.jpg" 61 | } 62 | }, 63 | { 64 | "id": "391jq9", 65 | "url": "https://wallhaven.cc/w/391jq9", 66 | "short_url": "https://whvn.cc/391jq9", 67 | "views": 22, 68 | "favorites": 0, 69 | "source": "", 70 | "purity": "sfw", 71 | "category": "general", 72 | "dimension_x": 4032, 73 | "dimension_y": 3024, 74 | "resolution": "4032x3024", 75 | "ratio": "1.33", 76 | "file_size": 3038902, 77 | "file_type": "image/jpeg", 78 | "created_at": "2020-06-19 14:20:01", 79 | "colors": [ 80 | "#999999", 81 | "#996633", 82 | "#424153", 83 | "#000000", 84 | "#663300" 85 | ], 86 | "path": "https://w.wallhaven.cc/full/39/wallhaven-391jq9.jpg", 87 | "thumbs": { 88 | "large": "https://th.wallhaven.cc/lg/39/391jq9.jpg", 89 | "original": "https://th.wallhaven.cc/orig/39/391jq9.jpg", 90 | "small": "https://th.wallhaven.cc/small/39/391jq9.jpg" 91 | } 92 | }, 93 | { 94 | "id": "6kr6dw", 95 | "url": "https://wallhaven.cc/w/6kr6dw", 96 | "short_url": "https://whvn.cc/6kr6dw", 97 | "views": 31, 98 | "favorites": 0, 99 | "source": "", 100 | "purity": "sfw", 101 | "category": "general", 102 | "dimension_x": 1920, 103 | "dimension_y": 1080, 104 | "resolution": "1920x1080", 105 | "ratio": "1.78", 106 | "file_size": 926364, 107 | "file_type": "image/jpeg", 108 | "created_at": "2020-06-19 13:50:19", 109 | "colors": [ 110 | "#424153", 111 | "#000000", 112 | "#999999", 113 | "#cccccc", 114 | "#996633" 115 | ], 116 | "path": "https://w.wallhaven.cc/full/6k/wallhaven-6kr6dw.jpg", 117 | "thumbs": { 118 | "large": "https://th.wallhaven.cc/lg/6k/6kr6dw.jpg", 119 | "original": "https://th.wallhaven.cc/orig/6k/6kr6dw.jpg", 120 | "small": "https://th.wallhaven.cc/small/6k/6kr6dw.jpg" 121 | } 122 | }, 123 | { 124 | "id": "g8egpq", 125 | "url": "https://wallhaven.cc/w/g8egpq", 126 | "short_url": "https://whvn.cc/g8egpq", 127 | "views": 13, 128 | "favorites": 0, 129 | "source": "", 130 | "purity": "sfw", 131 | "category": "general", 132 | "dimension_x": 1920, 133 | "dimension_y": 1080, 134 | "resolution": "1920x1080", 135 | "ratio": "1.78", 136 | "file_size": 882899, 137 | "file_type": "image/jpeg", 138 | "created_at": "2020-06-19 13:50:19", 139 | "colors": [ 140 | "#424153", 141 | "#000000", 142 | "#999999", 143 | "#cccccc", 144 | "#abbcda" 145 | ], 146 | "path": "https://w.wallhaven.cc/full/g8/wallhaven-g8egpq.jpg", 147 | "thumbs": { 148 | "large": "https://th.wallhaven.cc/lg/g8/g8egpq.jpg", 149 | "original": "https://th.wallhaven.cc/orig/g8/g8egpq.jpg", 150 | "small": "https://th.wallhaven.cc/small/g8/g8egpq.jpg" 151 | } 152 | }, 153 | { 154 | "id": "oxr85l", 155 | "url": "https://wallhaven.cc/w/oxr85l", 156 | "short_url": "https://whvn.cc/oxr85l", 157 | "views": 24, 158 | "favorites": 0, 159 | "source": "", 160 | "purity": "sfw", 161 | "category": "general", 162 | "dimension_x": 1920, 163 | "dimension_y": 1080, 164 | "resolution": "1920x1080", 165 | "ratio": "1.78", 166 | "file_size": 839060, 167 | "file_type": "image/jpeg", 168 | "created_at": "2020-06-19 13:50:19", 169 | "colors": [ 170 | "#000000", 171 | "#424153", 172 | "#999999", 173 | "#cccccc", 174 | "#996633" 175 | ], 176 | "path": "https://w.wallhaven.cc/full/ox/wallhaven-oxr85l.jpg", 177 | "thumbs": { 178 | "large": "https://th.wallhaven.cc/lg/ox/oxr85l.jpg", 179 | "original": "https://th.wallhaven.cc/orig/ox/oxr85l.jpg", 180 | "small": "https://th.wallhaven.cc/small/ox/oxr85l.jpg" 181 | } 182 | }, 183 | { 184 | "id": "7357pe", 185 | "url": "https://wallhaven.cc/w/7357pe", 186 | "short_url": "https://whvn.cc/7357pe", 187 | "views": 21, 188 | "favorites": 0, 189 | "source": "", 190 | "purity": "sfw", 191 | "category": "general", 192 | "dimension_x": 1920, 193 | "dimension_y": 1080, 194 | "resolution": "1920x1080", 195 | "ratio": "1.78", 196 | "file_size": 1153984, 197 | "file_type": "image/jpeg", 198 | "created_at": "2020-06-19 13:50:19", 199 | "colors": [ 200 | "#424153", 201 | "#999999", 202 | "#000000", 203 | "#996633", 204 | "#cccccc" 205 | ], 206 | "path": "https://w.wallhaven.cc/full/73/wallhaven-7357pe.jpg", 207 | "thumbs": { 208 | "large": "https://th.wallhaven.cc/lg/73/7357pe.jpg", 209 | "original": "https://th.wallhaven.cc/orig/73/7357pe.jpg", 210 | "small": "https://th.wallhaven.cc/small/73/7357pe.jpg" 211 | } 212 | }, 213 | { 214 | "id": "j5z7xw", 215 | "url": "https://wallhaven.cc/w/j5z7xw", 216 | "short_url": "https://whvn.cc/j5z7xw", 217 | "views": 23, 218 | "favorites": 0, 219 | "source": "", 220 | "purity": "sfw", 221 | "category": "general", 222 | "dimension_x": 1920, 223 | "dimension_y": 1080, 224 | "resolution": "1920x1080", 225 | "ratio": "1.78", 226 | "file_size": 746167, 227 | "file_type": "image/jpeg", 228 | "created_at": "2020-06-19 13:50:19", 229 | "colors": [ 230 | "#000000", 231 | "#424153", 232 | "#cccccc", 233 | "#999999", 234 | "#996633" 235 | ], 236 | "path": "https://w.wallhaven.cc/full/j5/wallhaven-j5z7xw.jpg", 237 | "thumbs": { 238 | "large": "https://th.wallhaven.cc/lg/j5/j5z7xw.jpg", 239 | "original": "https://th.wallhaven.cc/orig/j5/j5z7xw.jpg", 240 | "small": "https://th.wallhaven.cc/small/j5/j5z7xw.jpg" 241 | } 242 | }, 243 | { 244 | "id": "2eql8m", 245 | "url": "https://wallhaven.cc/w/2eql8m", 246 | "short_url": "https://whvn.cc/2eql8m", 247 | "views": 126, 248 | "favorites": 8, 249 | "source": "", 250 | "purity": "sfw", 251 | "category": "general", 252 | "dimension_x": 3840, 253 | "dimension_y": 1535, 254 | "resolution": "3840x1535", 255 | "ratio": "2.5", 256 | "file_size": 1043855, 257 | "file_type": "image/jpeg", 258 | "created_at": "2020-06-19 13:08:12", 259 | "colors": [ 260 | "#424153", 261 | "#000000", 262 | "#999999", 263 | "#996633", 264 | "#e7d8b1" 265 | ], 266 | "path": "https://w.wallhaven.cc/full/2e/wallhaven-2eql8m.jpg", 267 | "thumbs": { 268 | "large": "https://th.wallhaven.cc/lg/2e/2eql8m.jpg", 269 | "original": "https://th.wallhaven.cc/orig/2e/2eql8m.jpg", 270 | "small": "https://th.wallhaven.cc/small/2e/2eql8m.jpg" 271 | } 272 | }, 273 | { 274 | "id": "xlmg8d", 275 | "url": "https://wallhaven.cc/w/xlmg8d", 276 | "short_url": "https://whvn.cc/xlmg8d", 277 | "views": 249, 278 | "favorites": 10, 279 | "source": "", 280 | "purity": "sfw", 281 | "category": "general", 282 | "dimension_x": 3840, 283 | "dimension_y": 2160, 284 | "resolution": "3840x2160", 285 | "ratio": "1.78", 286 | "file_size": 14249335, 287 | "file_type": "image/png", 288 | "created_at": "2020-06-19 12:20:07", 289 | "colors": [ 290 | "#996633", 291 | "#999999", 292 | "#424153", 293 | "#663300", 294 | "#cccccc" 295 | ], 296 | "path": "https://w.wallhaven.cc/full/xl/wallhaven-xlmg8d.png", 297 | "thumbs": { 298 | "large": "https://th.wallhaven.cc/lg/xl/xlmg8d.jpg", 299 | "original": "https://th.wallhaven.cc/orig/xl/xlmg8d.jpg", 300 | "small": "https://th.wallhaven.cc/small/xl/xlmg8d.jpg" 301 | } 302 | }, 303 | { 304 | "id": "g8eg77", 305 | "url": "https://wallhaven.cc/w/g8eg77", 306 | "short_url": "https://whvn.cc/g8eg77", 307 | "views": 64, 308 | "favorites": 2, 309 | "source": "", 310 | "purity": "sfw", 311 | "category": "general", 312 | "dimension_x": 3000, 313 | "dimension_y": 2000, 314 | "resolution": "3000x2000", 315 | "ratio": "1.5", 316 | "file_size": 2065465, 317 | "file_type": "image/jpeg", 318 | "created_at": "2020-06-19 11:07:32", 319 | "colors": [ 320 | "#ff9900", 321 | "#f5dd01", 322 | "#cc0000", 323 | "#e7d8b1", 324 | "#424153" 325 | ], 326 | "path": "https://w.wallhaven.cc/full/g8/wallhaven-g8eg77.jpg", 327 | "thumbs": { 328 | "large": "https://th.wallhaven.cc/lg/g8/g8eg77.jpg", 329 | "original": "https://th.wallhaven.cc/orig/g8/g8eg77.jpg", 330 | "small": "https://th.wallhaven.cc/small/g8/g8eg77.jpg" 331 | } 332 | }, 333 | { 334 | "id": "vg6lg8", 335 | "url": "https://wallhaven.cc/w/vg6lg8", 336 | "short_url": "https://whvn.cc/vg6lg8", 337 | "views": 39, 338 | "favorites": 0, 339 | "source": "", 340 | "purity": "sfw", 341 | "category": "general", 342 | "dimension_x": 2000, 343 | "dimension_y": 1000, 344 | "resolution": "2000x1000", 345 | "ratio": "2", 346 | "file_size": 4014739, 347 | "file_type": "image/png", 348 | "created_at": "2020-06-19 10:45:27", 349 | "colors": [ 350 | "#000000", 351 | "#660000", 352 | "#996633", 353 | "#663300", 354 | "#cc6633" 355 | ], 356 | "path": "https://w.wallhaven.cc/full/vg/wallhaven-vg6lg8.png", 357 | "thumbs": { 358 | "large": "https://th.wallhaven.cc/lg/vg/vg6lg8.jpg", 359 | "original": "https://th.wallhaven.cc/orig/vg/vg6lg8.jpg", 360 | "small": "https://th.wallhaven.cc/small/vg/vg6lg8.jpg" 361 | } 362 | }, 363 | { 364 | "id": "2eqlem", 365 | "url": "https://wallhaven.cc/w/2eqlem", 366 | "short_url": "https://whvn.cc/2eqlem", 367 | "views": 85, 368 | "favorites": 3, 369 | "source": "", 370 | "purity": "sfw", 371 | "category": "anime", 372 | "dimension_x": 3840, 373 | "dimension_y": 2160, 374 | "resolution": "3840x2160", 375 | "ratio": "1.78", 376 | "file_size": 5865996, 377 | "file_type": "image/png", 378 | "created_at": "2020-06-19 10:41:47", 379 | "colors": [ 380 | "#e7d8b1", 381 | "#424153", 382 | "#663300", 383 | "#999999", 384 | "#996633" 385 | ], 386 | "path": "https://w.wallhaven.cc/full/2e/wallhaven-2eqlem.png", 387 | "thumbs": { 388 | "large": "https://th.wallhaven.cc/lg/2e/2eqlem.jpg", 389 | "original": "https://th.wallhaven.cc/orig/2e/2eqlem.jpg", 390 | "small": "https://th.wallhaven.cc/small/2e/2eqlem.jpg" 391 | } 392 | }, 393 | { 394 | "id": "dg7y5j", 395 | "url": "https://wallhaven.cc/w/dg7y5j", 396 | "short_url": "https://whvn.cc/dg7y5j", 397 | "views": 84, 398 | "favorites": 1, 399 | "source": "", 400 | "purity": "sfw", 401 | "category": "general", 402 | "dimension_x": 8192, 403 | "dimension_y": 4320, 404 | "resolution": "8192x4320", 405 | "ratio": "1.9", 406 | "file_size": 12935470, 407 | "file_type": "image/jpeg", 408 | "created_at": "2020-06-19 08:32:23", 409 | "colors": [ 410 | "#cc0000", 411 | "#000000", 412 | "#660000", 413 | "#990000", 414 | "#cc6633" 415 | ], 416 | "path": "https://w.wallhaven.cc/full/dg/wallhaven-dg7y5j.jpg", 417 | "thumbs": { 418 | "large": "https://th.wallhaven.cc/lg/dg/dg7y5j.jpg", 419 | "original": "https://th.wallhaven.cc/orig/dg/dg7y5j.jpg", 420 | "small": "https://th.wallhaven.cc/small/dg/dg7y5j.jpg" 421 | } 422 | }, 423 | { 424 | "id": "96lp5w", 425 | "url": "https://wallhaven.cc/w/96lp5w", 426 | "short_url": "https://whvn.cc/96lp5w", 427 | "views": 55, 428 | "favorites": 1, 429 | "source": "", 430 | "purity": "sfw", 431 | "category": "general", 432 | "dimension_x": 8192, 433 | "dimension_y": 4320, 434 | "resolution": "8192x4320", 435 | "ratio": "1.9", 436 | "file_size": 12782315, 437 | "file_type": "image/jpeg", 438 | "created_at": "2020-06-19 08:26:02", 439 | "colors": [ 440 | "#000000", 441 | "#424153", 442 | "#333399", 443 | "#0066cc", 444 | "#e7d8b1" 445 | ], 446 | "path": "https://w.wallhaven.cc/full/96/wallhaven-96lp5w.jpg", 447 | "thumbs": { 448 | "large": "https://th.wallhaven.cc/lg/96/96lp5w.jpg", 449 | "original": "https://th.wallhaven.cc/orig/96/96lp5w.jpg", 450 | "small": "https://th.wallhaven.cc/small/96/96lp5w.jpg" 451 | } 452 | }, 453 | { 454 | "id": "83ymxj", 455 | "url": "https://wallhaven.cc/w/83ymxj", 456 | "short_url": "https://whvn.cc/83ymxj", 457 | "views": 71, 458 | "favorites": 1, 459 | "source": "", 460 | "purity": "sfw", 461 | "category": "general", 462 | "dimension_x": 8192, 463 | "dimension_y": 4320, 464 | "resolution": "8192x4320", 465 | "ratio": "1.9", 466 | "file_size": 10841939, 467 | "file_type": "image/jpeg", 468 | "created_at": "2020-06-19 08:18:42", 469 | "colors": [ 470 | "#000000", 471 | "#424153", 472 | "#e7d8b1", 473 | "#999999", 474 | "#ffffff" 475 | ], 476 | "path": "https://w.wallhaven.cc/full/83/wallhaven-83ymxj.jpg", 477 | "thumbs": { 478 | "large": "https://th.wallhaven.cc/lg/83/83ymxj.jpg", 479 | "original": "https://th.wallhaven.cc/orig/83/83ymxj.jpg", 480 | "small": "https://th.wallhaven.cc/small/83/83ymxj.jpg" 481 | } 482 | }, 483 | { 484 | "id": "p8dz2j", 485 | "url": "https://wallhaven.cc/w/p8dz2j", 486 | "short_url": "https://whvn.cc/p8dz2j", 487 | "views": 210, 488 | "favorites": 1, 489 | "source": "", 490 | "purity": "sfw", 491 | "category": "general", 492 | "dimension_x": 1920, 493 | "dimension_y": 1080, 494 | "resolution": "1920x1080", 495 | "ratio": "1.78", 496 | "file_size": 3127767, 497 | "file_type": "image/png", 498 | "created_at": "2020-06-19 08:03:37", 499 | "colors": [ 500 | "#000000", 501 | "#424153", 502 | "#660000", 503 | "#990000", 504 | "#999999" 505 | ], 506 | "path": "https://w.wallhaven.cc/full/p8/wallhaven-p8dz2j.png", 507 | "thumbs": { 508 | "large": "https://th.wallhaven.cc/lg/p8/p8dz2j.jpg", 509 | "original": "https://th.wallhaven.cc/orig/p8/p8dz2j.jpg", 510 | "small": "https://th.wallhaven.cc/small/p8/p8dz2j.jpg" 511 | } 512 | }, 513 | { 514 | "id": "vg6lx8", 515 | "url": "https://wallhaven.cc/w/vg6lx8", 516 | "short_url": "https://whvn.cc/vg6lx8", 517 | "views": 129, 518 | "favorites": 1, 519 | "source": "", 520 | "purity": "sfw", 521 | "category": "general", 522 | "dimension_x": 1920, 523 | "dimension_y": 1282, 524 | "resolution": "1920x1282", 525 | "ratio": "1.5", 526 | "file_size": 794221, 527 | "file_type": "image/jpeg", 528 | "created_at": "2020-06-19 06:26:56", 529 | "colors": [ 530 | "#424153", 531 | "#000000", 532 | "#996633", 533 | "#663300", 534 | "#999999" 535 | ], 536 | "path": "https://w.wallhaven.cc/full/vg/wallhaven-vg6lx8.jpg", 537 | "thumbs": { 538 | "large": "https://th.wallhaven.cc/lg/vg/vg6lx8.jpg", 539 | "original": "https://th.wallhaven.cc/orig/vg/vg6lx8.jpg", 540 | "small": "https://th.wallhaven.cc/small/vg/vg6lx8.jpg" 541 | } 542 | }, 543 | { 544 | "id": "2eqlgm", 545 | "url": "https://wallhaven.cc/w/2eqlgm", 546 | "short_url": "https://whvn.cc/2eqlgm", 547 | "views": 170, 548 | "favorites": 8, 549 | "source": "", 550 | "purity": "sfw", 551 | "category": "general", 552 | "dimension_x": 2048, 553 | "dimension_y": 1365, 554 | "resolution": "2048x1365", 555 | "ratio": "1.5", 556 | "file_size": 1181161, 557 | "file_type": "image/jpeg", 558 | "created_at": "2020-06-19 06:25:13", 559 | "colors": [ 560 | "#999999", 561 | "#424153", 562 | "#cccccc", 563 | "#996633", 564 | "#e7d8b1" 565 | ], 566 | "path": "https://w.wallhaven.cc/full/2e/wallhaven-2eqlgm.jpg", 567 | "thumbs": { 568 | "large": "https://th.wallhaven.cc/lg/2e/2eqlgm.jpg", 569 | "original": "https://th.wallhaven.cc/orig/2e/2eqlgm.jpg", 570 | "small": "https://th.wallhaven.cc/small/2e/2eqlgm.jpg" 571 | } 572 | }, 573 | { 574 | "id": "wy7jo7", 575 | "url": "https://wallhaven.cc/w/wy7jo7", 576 | "short_url": "https://whvn.cc/wy7jo7", 577 | "views": 171, 578 | "favorites": 3, 579 | "source": "", 580 | "purity": "sfw", 581 | "category": "general", 582 | "dimension_x": 2048, 583 | "dimension_y": 1365, 584 | "resolution": "2048x1365", 585 | "ratio": "1.5", 586 | "file_size": 348781, 587 | "file_type": "image/jpeg", 588 | "created_at": "2020-06-19 06:24:26", 589 | "colors": [ 590 | "#000000", 591 | "#996633", 592 | "#336600", 593 | "#666600", 594 | "#663300" 595 | ], 596 | "path": "https://w.wallhaven.cc/full/wy/wallhaven-wy7jo7.jpg", 597 | "thumbs": { 598 | "large": "https://th.wallhaven.cc/lg/wy/wy7jo7.jpg", 599 | "original": "https://th.wallhaven.cc/orig/wy/wy7jo7.jpg", 600 | "small": "https://th.wallhaven.cc/small/wy/wy7jo7.jpg" 601 | } 602 | }, 603 | { 604 | "id": "dg7yxj", 605 | "url": "https://wallhaven.cc/w/dg7yxj", 606 | "short_url": "https://whvn.cc/dg7yxj", 607 | "views": 135, 608 | "favorites": 1, 609 | "source": "", 610 | "purity": "sfw", 611 | "category": "general", 612 | "dimension_x": 2048, 613 | "dimension_y": 1365, 614 | "resolution": "2048x1365", 615 | "ratio": "1.5", 616 | "file_size": 1012618, 617 | "file_type": "image/jpeg", 618 | "created_at": "2020-06-19 06:23:45", 619 | "colors": [ 620 | "#424153", 621 | "#336600", 622 | "#000000", 623 | "#666600", 624 | "#999999" 625 | ], 626 | "path": "https://w.wallhaven.cc/full/dg/wallhaven-dg7yxj.jpg", 627 | "thumbs": { 628 | "large": "https://th.wallhaven.cc/lg/dg/dg7yxj.jpg", 629 | "original": "https://th.wallhaven.cc/orig/dg/dg7yxj.jpg", 630 | "small": "https://th.wallhaven.cc/small/dg/dg7yxj.jpg" 631 | } 632 | }, 633 | { 634 | "id": "r2l131", 635 | "url": "https://wallhaven.cc/w/r2l131", 636 | "short_url": "https://whvn.cc/r2l131", 637 | "views": 335, 638 | "favorites": 8, 639 | "source": "", 640 | "purity": "sfw", 641 | "category": "general", 642 | "dimension_x": 2500, 643 | "dimension_y": 1732, 644 | "resolution": "2500x1732", 645 | "ratio": "1.44", 646 | "file_size": 2004200, 647 | "file_type": "image/jpeg", 648 | "created_at": "2020-06-19 06:22:53", 649 | "colors": [ 650 | "#424153", 651 | "#000000", 652 | "#999999", 653 | "#333399", 654 | "#abbcda" 655 | ], 656 | "path": "https://w.wallhaven.cc/full/r2/wallhaven-r2l131.jpg", 657 | "thumbs": { 658 | "large": "https://th.wallhaven.cc/lg/r2/r2l131.jpg", 659 | "original": "https://th.wallhaven.cc/orig/r2/r2l131.jpg", 660 | "small": "https://th.wallhaven.cc/small/r2/r2l131.jpg" 661 | } 662 | }, 663 | { 664 | "id": "ympo77", 665 | "url": "https://wallhaven.cc/w/ympo77", 666 | "short_url": "https://whvn.cc/ympo77", 667 | "views": 107, 668 | "favorites": 1, 669 | "source": "", 670 | "purity": "sfw", 671 | "category": "anime", 672 | "dimension_x": 2560, 673 | "dimension_y": 1440, 674 | "resolution": "2560x1440", 675 | "ratio": "1.78", 676 | "file_size": 600121, 677 | "file_type": "image/jpeg", 678 | "created_at": "2020-06-19 06:13:10", 679 | "colors": [ 680 | "#424153", 681 | "#999999", 682 | "#000000", 683 | "#cc6633", 684 | "#996633" 685 | ], 686 | "path": "https://w.wallhaven.cc/full/ym/wallhaven-ympo77.jpg", 687 | "thumbs": { 688 | "large": "https://th.wallhaven.cc/lg/ym/ympo77.jpg", 689 | "original": "https://th.wallhaven.cc/orig/ym/ympo77.jpg", 690 | "small": "https://th.wallhaven.cc/small/ym/ympo77.jpg" 691 | } 692 | } 693 | ], 694 | "meta": { 695 | "current_page": 1, 696 | "last_page": 13159, 697 | "per_page": "24", 698 | "total": 315796, 699 | "query": null, 700 | "seed": null 701 | } 702 | } 703 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 liang xiao 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 | IHaven 5 |

6 |

IHaven

7 |

🍭Awesome Wallpaper App Based on SwiftUI Power By Wallhaven aimed to macOS

8 |

9 | 10 | 14 | 18 | 22 | 26 |

30 |

Visit website IHaven or (release to)download. f^_^;

31 | 32 | # Features 33 | * Api from wallhacen.cc 34 | * Customized query conditions 35 | * Popover-style And Infinite-scroll 36 | * Quick / Free Download 37 | * More features comming soon ... 38 | # Screenshots 39 | - v1 40 |

41 | IHaven 42 |

43 | 44 | - v2 45 |

46 | IHaven 47 |

48 | 49 | # Future 50 | * Image Description View 51 | * Window Style Application 52 | * Localization 53 | * Dynastic Wallpaper Designer 54 | * Image Toolkit 55 | 56 | 57 | # Localization 58 | Ihaven will auto detect your system language and use the localization. 59 | 60 | You can set language manually in the Settings window. 61 | 62 | Now the app support $\color{red}{nothing}$ 😰 63 | 64 | # License 65 | IHaven itself is licensed under the MIT License license. 66 | -------------------------------------------------------------------------------- /screenshots/OgirinIdea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/screenshots/OgirinIdea.png -------------------------------------------------------------------------------- /screenshots/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/screenshots/logo.png -------------------------------------------------------------------------------- /screenshots/snapshot-2020-06-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/screenshots/snapshot-2020-06-22.png -------------------------------------------------------------------------------- /screenshots/timg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/screenshots/timg.jpeg -------------------------------------------------------------------------------- /screenshots/v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/screenshots/v1.png -------------------------------------------------------------------------------- /screenshots/v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laxse/IHaven/a8cdcafcdc7928f46ca24f00038844046ca7f333/screenshots/v2.png --------------------------------------------------------------------------------