diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3123b5c2..768ee159 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,7 +6,7 @@ on: types: [closed] env: DEVELOPER_DIR: /Applications/Xcode_13.1.app - APP_VERSION: '1.3.8' + APP_VERSION: '1.4.0' SCHEME_NAME: 'EhPanda' ALTSTORE_JSON_PATH: './AltStore.json' BUILDS_PATH: '/tmp/action-builds' diff --git a/EhPanda.xcodeproj/project.pbxproj b/EhPanda.xcodeproj/project.pbxproj index 9b6fb293..3ae552f7 100644 --- a/EhPanda.xcodeproj/project.pbxproj +++ b/EhPanda.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ AB10117E26986B7D00C2C1A9 /* GalleryStateMO+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB10117D26986B7D00C2C1A9 /* GalleryStateMO+CoreDataProperties.swift */; }; AB10118026986C1100C2C1A9 /* GalleryStateMO+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB10117F26986C1100C2C1A9 /* GalleryStateMO+CoreDataClass.swift */; }; AB19D619266E5C6700BA752A /* TTProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = AB19D618266E5C6700BA752A /* TTProgressHUD */; }; + AB21CC9A274B4ED700C115B1 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = AB21CC99274B4ED700C115B1 /* Kingfisher */; }; + AB21CCA0274B4F0C00C115B1 /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = AB21CC9F274B4F0C00C115B1 /* SwiftyBeaver */; }; AB2CED64268AB6AE003130F7 /* GalleryMO+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB2CED63268AB6AE003130F7 /* GalleryMO+CoreDataProperties.swift */; }; AB30E34826D277F7007420BC /* Toplists.html in Resources */ = {isa = PBXBuildFile; fileRef = AB30E34726D277F7007420BC /* Toplists.html */; }; AB358311269D7B63009466A5 /* DFURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB358310269D7B63009466A5 /* DFURLProtocol.swift */; }; @@ -41,13 +43,15 @@ AB4FD2C1268AB83300A95968 /* GalleryDetailMO+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB4FD2C0268AB83300A95968 /* GalleryDetailMO+CoreDataProperties.swift */; }; AB5BE67926B95FDD007D4A55 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB5BE67826B95FDD007D4A55 /* ShareViewController.swift */; }; AB5BE68026B95FDD007D4A55 /* ShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = AB5BE67626B95FDD007D4A55 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + AB60D0CF274C7AA000F899AB /* BetterCodable in Frameworks */ = {isa = PBXBuildFile; productRef = AB60D0CE274C7AA000F899AB /* BetterCodable */; }; + AB60D0E9274C7ECE00F899AB /* WaterfallGrid in Frameworks */ = {isa = PBXBuildFile; productRef = AB60D0E8274C7ECE00F899AB /* WaterfallGrid */; }; + AB60D0EB274CFB6D00F899AB /* SuggestionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB60D0EA274CFB6D00F899AB /* SuggestionProvider.swift */; }; AB63EADB2699AC8200090535 /* AppEnvMO+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB63EADA2699AC8200090535 /* AppEnvMO+CoreDataProperties.swift */; }; AB63EADD2699AC9100090535 /* AppEnvMO+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB63EADC2699AC9100090535 /* AppEnvMO+CoreDataClass.swift */; }; AB6505A026B0027800F91E9D /* SwiftUIPager in Frameworks */ = {isa = PBXBuildFile; productRef = AB65059F26B0027800F91E9D /* SwiftUIPager */; }; AB69CB8026B3DABC00699359 /* AdvancedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB69CB7F26B3DABC00699359 /* AdvancedList.swift */; }; AB69CB8226B3DAF400699359 /* ControlPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB69CB8126B3DAF400699359 /* ControlPanel.swift */; }; AB6DE897268822390087C579 /* LogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB6DE896268822390087C579 /* LogsView.swift */; }; - AB73CEAD26AAB8C600EF6337 /* BetterCodable in Frameworks */ = {isa = PBXBuildFile; productRef = AB73CEAC26AAB8C600EF6337 /* BetterCodable */; }; AB73CEAF26AAC13F00EF6337 /* CodableExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB73CEAE26AAC13F00EF6337 /* CodableExtension.swift */; }; AB7B29F226AC471E00EE1F14 /* MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7B29F126AC471E00EE1F14 /* MigrationPolicy.swift */; }; AB7B29F626AC741600EE1F14 /* GenericList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7B29F526AC741600EE1F14 /* GenericList.swift */; }; @@ -57,7 +61,6 @@ ABA732DF25A852D800B3D9AB /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA732DE25A852D800B3D9AB /* Filter.swift */; }; ABAC82FE26BC4A96009F5026 /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = ABAC82FD26BC4A96009F5026 /* OpenCC */; }; ABAFFE4026A86E3000EE8661 /* MeasureTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABAFFE3F26A86E3000EE8661 /* MeasureTool.swift */; }; - ABB944DD26DBBB1800C365C1 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = ABB944DC26DBBB1800C365C1 /* Kingfisher */; }; ABBC332826BE31AE0084A331 /* EhSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABBC332726BE31AE0084A331 /* EhSettingView.swift */; }; ABBC332A26BE7C940084A331 /* SettingTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABBC332926BE7C940084A331 /* SettingTextField.swift */; }; ABBCCC9026C95F6E007D8A36 /* GalleryInfosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABBCCC8F26C95F6E007D8A36 /* GalleryInfosView.swift */; }; @@ -77,13 +80,11 @@ ABCA93C42692A0BF00A98BC6 /* PersistenceAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCA93C32692A0BF00A98BC6 /* PersistenceAccessor.swift */; }; ABCD2F0A259763FC008E5A20 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD2F09259763FC008E5A20 /* Request.swift */; }; ABCD2F0E25976B95008E5A20 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD2F0D25976B95008E5A20 /* Parser.swift */; }; - ABD4032426B6EC6800001B8C /* WaterfallGrid in Frameworks */ = {isa = PBXBuildFile; productRef = ABD4032326B6EC6800001B8C /* WaterfallGrid */; }; ABD4032626B78E5A00001B8C /* GalleryThumbnailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABD4032526B78E5A00001B8C /* GalleryThumbnailCell.swift */; }; ABD4032826B7967F00001B8C /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABD4032726B7967F00001B8C /* Category.swift */; }; ABD5FDD4263D05110021A4C6 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = ABD5FDD3263D05110021A4C6 /* .swiftlint.yml */; }; ABD7005926B1C31500DC59C9 /* Kanna in Frameworks */ = {isa = PBXBuildFile; productRef = ABD7005826B1C31500DC59C9 /* Kanna */; }; ABE1867826A1733000689FDC /* LaboratorySettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABE1867726A1733000689FDC /* LaboratorySettingView.swift */; }; - ABE1867F26A18DD000689FDC /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = ABE1867E26A18DD000689FDC /* SwiftyBeaver */; }; ABE2AE752699F238001D47AA /* AppEnvStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABE2AE742699F238001D47AA /* AppEnvStorage.swift */; }; ABE9401526FF158D0085E158 /* QuickSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABE9401426FF158D0085E158 /* QuickSearchView.swift */; }; ABE9402D26FF89220085E158 /* AlertKit in Frameworks */ = {isa = PBXBuildFile; productRef = ABE9402C26FF89220085E158 /* AlertKit */; }; @@ -194,6 +195,7 @@ AB5BE67626B95FDD007D4A55 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; AB5BE67826B95FDD007D4A55 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; AB5BE67D26B95FDD007D4A55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AB60D0EA274CFB6D00F899AB /* SuggestionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionProvider.swift; sourceTree = ""; }; AB63EADA2699AC8200090535 /* AppEnvMO+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppEnvMO+CoreDataProperties.swift"; sourceTree = ""; }; AB63EADC2699AC9100090535 /* AppEnvMO+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppEnvMO+CoreDataClass.swift"; sourceTree = ""; }; AB69CB7F26B3DABC00699359 /* AdvancedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedList.swift; sourceTree = ""; }; @@ -298,13 +300,13 @@ ABD7005926B1C31500DC59C9 /* Kanna in Frameworks */, AB19D619266E5C6700BA752A /* TTProgressHUD in Frameworks */, ABE9402D26FF89220085E158 /* AlertKit in Frameworks */, + AB60D0E9274C7ECE00F899AB /* WaterfallGrid in Frameworks */, AB0F68AF26A6D92F00AC3A54 /* DeprecatedAPI in Frameworks */, - ABB944DD26DBBB1800C365C1 /* Kingfisher in Frameworks */, + AB21CC9A274B4ED700C115B1 /* Kingfisher in Frameworks */, + AB60D0CF274C7AA000F899AB /* BetterCodable in Frameworks */, AB6505A026B0027800F91E9D /* SwiftUIPager in Frameworks */, - ABD4032426B6EC6800001B8C /* WaterfallGrid in Frameworks */, + AB21CCA0274B4F0C00C115B1 /* SwiftyBeaver in Frameworks */, ABAC82FE26BC4A96009F5026 /* OpenCC in Frameworks */, - AB73CEAD26AAB8C600EF6337 /* BetterCodable in Frameworks */, - ABE1867F26A18DD000689FDC /* SwiftyBeaver in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -586,6 +588,7 @@ ABF45AC725F3313D00ECB568 /* TagCloudView.swift */, ABBC332926BE7C940084A331 /* SettingTextField.swift */, AB0ABCB626C541A400AD970F /* WaveForm.swift */, + AB60D0EA274CFB6D00F899AB /* SuggestionProvider.swift */, ); path = Tools; sourceTree = ""; @@ -673,15 +676,15 @@ name = EhPanda; packageProductDependencies = ( AB19D618266E5C6700BA752A /* TTProgressHUD */, - ABE1867E26A18DD000689FDC /* SwiftyBeaver */, AB0F68AE26A6D92F00AC3A54 /* DeprecatedAPI */, - AB73CEAC26AAB8C600EF6337 /* BetterCodable */, AB65059F26B0027800F91E9D /* SwiftUIPager */, ABD7005826B1C31500DC59C9 /* Kanna */, - ABD4032326B6EC6800001B8C /* WaterfallGrid */, ABAC82FD26BC4A96009F5026 /* OpenCC */, - ABB944DC26DBBB1800C365C1 /* Kingfisher */, ABE9402C26FF89220085E158 /* AlertKit */, + AB21CC99274B4ED700C115B1 /* Kingfisher */, + AB21CC9F274B4F0C00C115B1 /* SwiftyBeaver */, + AB60D0CE274C7AA000F899AB /* BetterCodable */, + AB60D0E8274C7ECE00F899AB /* WaterfallGrid */, ); productName = EhPanda; productReference = ABC3C7542593696C00E0C11B /* EhPanda.app */; @@ -742,15 +745,15 @@ mainGroup = ABC3C74B2593696C00E0C11B; packageReferences = ( AB19D617266E5C6700BA752A /* XCRemoteSwiftPackageReference "TTProgressHUD" */, - ABE1867D26A18DD000689FDC /* XCRemoteSwiftPackageReference "SwiftyBeaver" */, AB0F68AD26A6D92F00AC3A54 /* XCRemoteSwiftPackageReference "DeprecatedAPI" */, - AB73CEAB26AAB8C600EF6337 /* XCRemoteSwiftPackageReference "BetterCodable" */, AB65059E26B0027800F91E9D /* XCRemoteSwiftPackageReference "SwiftUIPager" */, ABD7005726B1C31500DC59C9 /* XCRemoteSwiftPackageReference "Kanna" */, - ABD4032226B6EC6800001B8C /* XCRemoteSwiftPackageReference "WaterfallGrid" */, ABAC82FC26BC4866009F5026 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */, - ABB944DB26DBBB1800C365C1 /* XCRemoteSwiftPackageReference "Kingfisher" */, ABE9402B26FF89220085E158 /* XCRemoteSwiftPackageReference "AlertKit" */, + AB21CC98274B4ED700C115B1 /* XCRemoteSwiftPackageReference "Kingfisher" */, + AB21CC9E274B4F0C00C115B1 /* XCRemoteSwiftPackageReference "SwiftyBeaver" */, + AB60D0CD274C7AA000F899AB /* XCRemoteSwiftPackageReference "BetterCodable" */, + AB60D0E7274C7ECE00F899AB /* XCRemoteSwiftPackageReference "WaterfallGrid" */, ); productRefGroup = ABC3C7552593696C00E0C11B /* Products */; projectDirPath = ""; @@ -859,6 +862,7 @@ AB63EADD2699AC9100090535 /* AppEnvMO+CoreDataClass.swift in Sources */, ABCA93BE26918DE100A98BC6 /* Persistence.swift in Sources */, ABF45AEF25F3313D00ECB568 /* TorrentsView.swift in Sources */, + AB60D0EB274CFB6D00F899AB /* SuggestionProvider.swift in Sources */, ABF45AB925F3312F00ECB568 /* AppState.swift in Sources */, ABF45AF625F3313D00ECB568 /* AppearanceSettingView.swift in Sources */, AB69CB8226B3DAF400699359 /* ControlPanel.swift in Sources */, @@ -1318,48 +1322,56 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/tatsuz0u/TTProgressHUD.git"; requirement = { - branch = master; + branch = custom; kind = branch; }; }; - AB65059E26B0027800F91E9D /* XCRemoteSwiftPackageReference "SwiftUIPager" */ = { + AB21CC98274B4ED700C115B1 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/fermoya/SwiftUIPager.git"; + repositoryURL = "https://github.com/tatsuz0u/Kingfisher.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.0; + branch = "fix/binder-loaded"; + kind = branch; + }; + }; + AB21CC9E274B4F0C00C115B1 /* XCRemoteSwiftPackageReference "SwiftyBeaver" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/tatsuz0u/SwiftyBeaver.git"; + requirement = { + branch = custom; + kind = branch; }; }; - AB73CEAB26AAB8C600EF6337 /* XCRemoteSwiftPackageReference "BetterCodable" */ = { + AB60D0CD274C7AA000F899AB /* XCRemoteSwiftPackageReference "BetterCodable" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/marksands/BetterCodable"; + repositoryURL = "https://github.com/marksands/BetterCodable.git"; requirement = { kind = upToNextMajorVersion; minimumVersion = 0.4.0; }; }; - ABAC82FC26BC4866009F5026 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */ = { + AB60D0E7274C7ECE00F899AB /* XCRemoteSwiftPackageReference "WaterfallGrid" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ddddxxx/SwiftyOpenCC.git"; + repositoryURL = "https://github.com/paololeonardi/WaterfallGrid.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "2.0.0-beta"; + minimumVersion = 1.0.1; }; }; - ABB944DB26DBBB1800C365C1 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + AB65059E26B0027800F91E9D /* XCRemoteSwiftPackageReference "SwiftUIPager" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/tatsuz0u/Kingfisher.git"; + repositoryURL = "https://github.com/fermoya/SwiftUIPager.git"; requirement = { - branch = "fix/binder-loaded"; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; }; }; - ABD4032226B6EC6800001B8C /* XCRemoteSwiftPackageReference "WaterfallGrid" */ = { + ABAC82FC26BC4866009F5026 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/paololeonardi/WaterfallGrid.git"; + repositoryURL = "https://github.com/ddddxxx/SwiftyOpenCC.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.0.0; + minimumVersion = "2.0.0-beta"; }; }; ABD7005726B1C31500DC59C9 /* XCRemoteSwiftPackageReference "Kanna" */ = { @@ -1370,14 +1382,6 @@ minimumVersion = 5.0.0; }; }; - ABE1867D26A18DD000689FDC /* XCRemoteSwiftPackageReference "SwiftyBeaver" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SwiftyBeaver/SwiftyBeaver.git"; - requirement = { - branch = master; - kind = branch; - }; - }; ABE9402B26FF89220085E158 /* XCRemoteSwiftPackageReference "AlertKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/tatsuz0u/AlertKit.git"; @@ -1399,41 +1403,41 @@ package = AB19D617266E5C6700BA752A /* XCRemoteSwiftPackageReference "TTProgressHUD" */; productName = TTProgressHUD; }; - AB65059F26B0027800F91E9D /* SwiftUIPager */ = { + AB21CC99274B4ED700C115B1 /* Kingfisher */ = { isa = XCSwiftPackageProductDependency; - package = AB65059E26B0027800F91E9D /* XCRemoteSwiftPackageReference "SwiftUIPager" */; - productName = SwiftUIPager; + package = AB21CC98274B4ED700C115B1 /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; }; - AB73CEAC26AAB8C600EF6337 /* BetterCodable */ = { + AB21CC9F274B4F0C00C115B1 /* SwiftyBeaver */ = { isa = XCSwiftPackageProductDependency; - package = AB73CEAB26AAB8C600EF6337 /* XCRemoteSwiftPackageReference "BetterCodable" */; + package = AB21CC9E274B4F0C00C115B1 /* XCRemoteSwiftPackageReference "SwiftyBeaver" */; + productName = SwiftyBeaver; + }; + AB60D0CE274C7AA000F899AB /* BetterCodable */ = { + isa = XCSwiftPackageProductDependency; + package = AB60D0CD274C7AA000F899AB /* XCRemoteSwiftPackageReference "BetterCodable" */; productName = BetterCodable; }; - ABAC82FD26BC4A96009F5026 /* OpenCC */ = { + AB60D0E8274C7ECE00F899AB /* WaterfallGrid */ = { isa = XCSwiftPackageProductDependency; - package = ABAC82FC26BC4866009F5026 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */; - productName = OpenCC; + package = AB60D0E7274C7ECE00F899AB /* XCRemoteSwiftPackageReference "WaterfallGrid" */; + productName = WaterfallGrid; }; - ABB944DC26DBBB1800C365C1 /* Kingfisher */ = { + AB65059F26B0027800F91E9D /* SwiftUIPager */ = { isa = XCSwiftPackageProductDependency; - package = ABB944DB26DBBB1800C365C1 /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; + package = AB65059E26B0027800F91E9D /* XCRemoteSwiftPackageReference "SwiftUIPager" */; + productName = SwiftUIPager; }; - ABD4032326B6EC6800001B8C /* WaterfallGrid */ = { + ABAC82FD26BC4A96009F5026 /* OpenCC */ = { isa = XCSwiftPackageProductDependency; - package = ABD4032226B6EC6800001B8C /* XCRemoteSwiftPackageReference "WaterfallGrid" */; - productName = WaterfallGrid; + package = ABAC82FC26BC4866009F5026 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */; + productName = OpenCC; }; ABD7005826B1C31500DC59C9 /* Kanna */ = { isa = XCSwiftPackageProductDependency; package = ABD7005726B1C31500DC59C9 /* XCRemoteSwiftPackageReference "Kanna" */; productName = Kanna; }; - ABE1867E26A18DD000689FDC /* SwiftyBeaver */ = { - isa = XCSwiftPackageProductDependency; - package = ABE1867D26A18DD000689FDC /* XCRemoteSwiftPackageReference "SwiftyBeaver" */; - productName = SwiftyBeaver; - }; ABE9402C26FF89220085E158 /* AlertKit */ = { isa = XCSwiftPackageProductDependency; package = ABE9402B26FF89220085E158 /* XCRemoteSwiftPackageReference "AlertKit" */; diff --git a/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a9e1e070..0eeacaed 100644 --- a/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -12,7 +12,7 @@ }, { "package": "BetterCodable", - "repositoryURL": "https://github.com/marksands/BetterCodable", + "repositoryURL": "https://github.com/marksands/BetterCodable.git", "state": { "branch": null, "revision": "61153170668db7a46a20a87e35e70f80b24d4eb5", @@ -42,7 +42,7 @@ "repositoryURL": "https://github.com/tatsuz0u/Kingfisher.git", "state": { "branch": "fix/binder-loaded", - "revision": "184d0968c9cbefde319f47e992913ef7bc64de5c", + "revision": "57744dfc08e6594f3df4490b12a89746ebf0f094", "version": null } }, @@ -51,16 +51,16 @@ "repositoryURL": "https://github.com/fermoya/SwiftUIPager.git", "state": { "branch": null, - "revision": "d6745593cfa38ffa1fca75244a01e422d220d407", - "version": "2.3.1" + "revision": "3c0485ffc369f2138477ec508cafc5fffd39f2bf", + "version": "2.3.2" } }, { "package": "SwiftyBeaver", - "repositoryURL": "https://github.com/SwiftyBeaver/SwiftyBeaver.git", + "repositoryURL": "https://github.com/tatsuz0u/SwiftyBeaver.git", "state": { - "branch": "master", - "revision": "2a2e1552c41d2e82b86f5822a95a03d0587e0683", + "branch": "custom", + "revision": "2e8a65567ca877a7fdf6a63ab53b2c49a927af87", "version": null } }, @@ -77,8 +77,8 @@ "package": "TTProgressHUD", "repositoryURL": "https://github.com/tatsuz0u/TTProgressHUD.git", "state": { - "branch": "master", - "revision": "1d6360603a1f3d7b8dac9ecd46f3ed504d19c2bd", + "branch": "custom", + "revision": "349b595c4f0ff86e8d3c8d65be206a02642fd525", "version": null } }, diff --git a/EhPanda/App/Defaults.swift b/EhPanda/App/Defaults.swift index 6e8d8afa..f6315133 100644 --- a/EhPanda/App/Defaults.swift +++ b/EhPanda/App/Defaults.swift @@ -21,17 +21,19 @@ struct Defaults { DeviceUtil.isPadWidth ? 175 : DeviceUtil.isSEWidth ? 125 : 150 } struct ImageSize { - static let rowScale: CGFloat = 8/11 - static let avatarScale: CGFloat = 1/1 - static let headerScale: CGFloat = 8/11 - static let previewScale: CGFloat = 8/11 - static let contentScale: CGFloat = 7/10 + static let rowAspect: CGFloat = 8/11 + static let avatarAspect: CGFloat = 1/1 + static let headerAspect: CGFloat = 8/11 + static let previewAspect: CGFloat = 8/11 + static let contentAspect: CGFloat = 7/10 + static let webtoonMinAspect: CGFloat = 1/4 + static let webtoonIdealAspect: CGFloat = 2/3 - static let rowW: CGFloat = rowH * rowScale + static let rowW: CGFloat = rowH * rowAspect static let rowH: CGFloat = 110 static let avatarW: CGFloat = 100 static let avatarH: CGFloat = 100 - static let headerW: CGFloat = headerH * headerScale + static let headerW: CGFloat = headerH * headerAspect static let headerH: CGFloat = 150 static let previewMinW: CGFloat = DeviceUtil.isPadWidth ? 180 : 100 static let previewMaxW: CGFloat = DeviceUtil.isPadWidth ? 220 : 120 diff --git a/EhPanda/App/Extensions.swift b/EhPanda/App/Extensions.swift index 5f4be59e..d857954d 100644 --- a/EhPanda/App/Extensions.swift +++ b/EhPanda/App/Extensions.swift @@ -137,8 +137,8 @@ extension UIImage { return UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation) } - func cropping(size: CGSize, offset: CGFloat) -> UIImage? { - let origin = CGPoint(x: offset, y: 0) + func cropping(size: CGSize, offset: CGSize) -> UIImage? { + let origin = CGPoint(x: offset.width, y: offset.height) let rect = CGRect(origin: origin, size: size) return cropping(to: rect) } diff --git a/EhPanda/App/Tools/Parser.swift b/EhPanda/App/Tools/Parser.swift index ec131e4a..06d94ac9 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -93,10 +93,21 @@ struct Parser { throw AppError.parseFailed } + func parseUploader(node: XMLElement?) throws -> String? { + guard let divNode = node?.at_xpath("//td [@class='gl4c glhide']")?.at_xpath("//div") else { + throw AppError.parseFailed + } + + if let aText = divNode.at_xpath("//a")?.text { + return aText + } else { + return divNode.text + } + } + var galleryItems = [Gallery]() for link in doc.xpath("//tr") { - let firstDivNode = link.at_xpath("//td [@class='gl4c glhide']")?.at_xpath("//div") - let uploader = firstDivNode?.at_xpath("//a")?.text + let uploader = try? parseUploader(node: link) guard let gl2cNode = link.at_xpath("//td [@class='gl2c']"), let gl3cNode = link.at_xpath("//td [@class='gl3c glname']"), let (rating, _, _) = try? parseRating(node: gl2cNode), @@ -303,6 +314,20 @@ struct Parser { return .no(reason: reason) } + func parseUploader(node: XMLElement?) throws -> String { + guard let gdnNode = node?.at_xpath("//div [@id='gdn']") else { + throw AppError.parseFailed + } + + if let aText = gdnNode.at_xpath("//a")?.text { + return aText + } else if let gdnText = gdnNode.text { + return gdnText + } else { + throw AppError.parseFailed + } + } + var tmpGalleryDetail: GalleryDetail? var tmpGalleryState: GalleryState? for link in doc.xpath("//div [@class='gm']") { @@ -324,7 +349,7 @@ struct Parser { let favoredCount = Int(infoPanel[7]), let language = Language(rawValue: infoPanel[3]), let engTitle = link.at_xpath("//h1 [@id='gn']")?.text, - let uploader = gd3Node.at_xpath("//div [@id='gdn']")?.at_xpath("//a")?.text, + let uploader = try? parseUploader(node: gd3Node), let (imgRating, textRating, containsUserRating) = try? parseRating(node: gdrNode), let ratingCount = Int(gdrNode.at_xpath("//span [@id='rating_count']")?.text ?? ""), let category = Category(rawValue: gd3Node.at_xpath("//div [@id='gdc']")?.text ?? ""), @@ -1284,16 +1309,13 @@ extension Parser { var profileNotFound = true var profileValue: Int? - let selector = doc.at_xpath( - "//select [@name='profile_set']" - ) + let selector = doc.at_xpath("//select [@name='profile_set']") let options = selector?.xpath("//option") - guard let options = options, - options.count >= 1 + guard let options = options, options.count >= 1 else { throw AppError.parseFailed } - for link in options where link.text == "EhPanda" { + for link in options where AppUtil.verifyEhPandaProfileName(with: link.text) { profileNotFound = false profileValue = Int(link["value"] ?? "") } @@ -1486,7 +1508,7 @@ extension Parser { } // MARK: parsePreviewConfigs - static func parsePreviewConfigs(string: String) -> (String, CGSize, CGFloat)? { + static func parsePreviewConfigs(string: String) -> (String, CGSize, CGSize)? { guard let rangeA = string.range(of: Defaults.PreviewIdentifier.width), let rangeB = string.range(of: Defaults.PreviewIdentifier.height), let rangeC = string.range(of: Defaults.PreviewIdentifier.offset) @@ -1495,11 +1517,11 @@ extension Parser { let plainURL = String(string[.. Bool { + ["EhPanda", "EhPanda (Default)"].contains(name ?? "") + } + static func configureKingfisher(bypassesSNIFiltering: Bool, handlesCookies: Bool = true) { let config = KingfisherManager.shared.downloader.sessionConfiguration if handlesCookies { config.httpCookieStorage = HTTPCookieStorage.shared } diff --git a/EhPanda/App/ViewModifiers.swift b/EhPanda/App/ViewModifiers.swift index 2a410243..7d664e1f 100644 --- a/EhPanda/App/ViewModifiers.swift +++ b/EhPanda/App/ViewModifiers.swift @@ -10,11 +10,10 @@ import Kingfisher extension View { func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { - clipShape( RoundedCorner(radius: radius, corners: corners) ) + clipShape(RoundedCorner(radius: radius, corners: corners)) } - @ViewBuilder - func withHorizontalSpacing(height: CGFloat? = nil) -> some View { + @ViewBuilder func withHorizontalSpacing(height: CGFloat? = nil) -> some View { Color.clear.frame(width: 8, height: height) self Color.clear.frame(width: 8, height: height) @@ -62,56 +61,58 @@ struct CornersModifier: ImageModifier { struct OffsetModifier: ImageModifier { private let size: CGSize? - private let offset: CGFloat? + private let offset: CGSize? - init(size: CGSize?, offset: CGFloat?) { + init(size: CGSize?, offset: CGSize?) { self.size = size self.offset = offset } - func modify( - _ image: KFCrossPlatformImage - ) -> KFCrossPlatformImage - { - guard let size = size, - let offset = offset + func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + guard let size = size, let offset = offset else { return image } - return image.cropping( - size: size, offset: offset - ) ?? image + return image.cropping(size: size, offset: offset) ?? image } } struct RoundedOffsetModifier: ImageModifier { private let size: CGSize? - private let offset: CGFloat? + private let offset: CGSize? - init(size: CGSize?, offset: CGFloat?) { + init(size: CGSize?, offset: CGSize?) { self.size = size self.offset = offset } - func modify( - _ image: KFCrossPlatformImage - ) -> KFCrossPlatformImage - { - guard let size = size, - let offset = offset, - let croppedImg = image.cropping( - size: size, offset: offset - ), - let roundedCroppedImg = croppedImg - .withRoundedCorners(radius: 5) - else { - return image - .withRoundedCorners(radius: 5) ?? image - } + func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + guard let size = size, let offset = offset, + let croppedImg = image.cropping(size: size, offset: offset), + let roundedCroppedImg = croppedImg.withRoundedCorners(radius: 5) + else { return image.withRoundedCorners(radius: 5) ?? image } return roundedCroppedImg } } +struct WebtoonModifier: ImageModifier { + private let minAspect: CGFloat + private let idealAspect: CGFloat + + init(minAspect: CGFloat, idealAspect: CGFloat) { + self.minAspect = minAspect + self.idealAspect = idealAspect + } + + func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage { + let width = image.size.width + let height = image.size.height + let idealHeight = width / idealAspect + guard width / height < minAspect else { return image } + return image.cropping(size: CGSize(width: width, height: idealHeight), offset: .zero) ?? image + } +} + extension KFImage { func defaultModifier(withRoundedCorners: Bool = true) -> KFImage { self diff --git a/EhPanda/App/de.lproj/Localizable.strings b/EhPanda/App/de.lproj/Localizable.strings index d0d85e91..42b9ecc7 100644 --- a/EhPanda/App/de.lproj/Localizable.strings +++ b/EhPanda/App/de.lproj/Localizable.strings @@ -50,6 +50,9 @@ //"Jump page" = ""; //"Confirm" = ""; +// MARK: HomeView +//"Clear history" = ""; + // MARK: DetailView "Archive" = "Archiv"; "Torrents" = "Torrents"; @@ -120,11 +123,9 @@ //"Username" = ""; //"Password" = ""; "Logout" = "Ausloggen"; +"Are you sure to logout?" = "Bist du sicher das du dich ausloggen möchtest?"; "Account configuration" = "Kontoeinstellungen"; "Manage tags subscription" = "Meine Tags bearbeiten"; -"Are you sure to logout?" = "Bist du sicher das du dich ausloggen möchtest?"; -"Are you sure to clear?" = "Bist du sicher das du das löschen möchtest?"; -"Clear" = "Löschen"; "Copy cookies" = "Cookies kopieren"; "General" = "Allgemein"; @@ -135,6 +136,8 @@ "Auto-Lock" = "Auto-Lock"; "App switcher blur" = "Appinhalt bei Appwechsel unkenntlich machen"; "Cache" = "Cache"; +"Clear" = "Löschen"; +"Are you sure to clear?" = "Bist du sicher das du das löschen möchtest?"; "Clear image caches" = "Zwischengespeicherte Bilder (Cache) löschen"; "Appearance" = "Oberfläche"; diff --git a/EhPanda/App/ja.lproj/Localizable.strings b/EhPanda/App/ja.lproj/Localizable.strings index ded18fd0..4361fe2e 100644 --- a/EhPanda/App/ja.lproj/Localizable.strings +++ b/EhPanda/App/ja.lproj/Localizable.strings @@ -50,6 +50,9 @@ "Jump page" = "ページジャンプ"; "Confirm" = "確認"; +// MARK: HomeView +"Clear history" = "履歴を削除"; + // MARK: DetailView "Archive" = "アーカイブ"; "Torrents" = "トレント"; @@ -120,11 +123,9 @@ "Username" = "ユーザー名"; "Password" = "パスワード"; "Logout" = "ログアウト"; +"Are you sure to logout?" = "本当にログアウトしますか?"; "Account configuration" = "アカウント設定"; "Manage tags subscription" = "タグの購読を管理"; -"Are you sure to logout?" = "本当にログアウトしますか?"; -"Are you sure to clear?" = "本当に削除しますか?"; -"Clear" = "削除"; "Copy cookies" = "クッキーをコピー"; "General" = "一般"; @@ -135,7 +136,9 @@ "Auto-Lock" = "自動ロック"; "App switcher blur" = "アプリスイッチャーぼかし"; "Cache" = "キャッシュ"; +"Clear" = "削除"; "Clear image caches" = "画像キャッシュを削除"; +"Are you sure to clear?" = "本当に削除しますか?"; "Appearance" = "外観"; "Global" = "全般"; diff --git a/EhPanda/App/ko.lproj/Localizable.strings b/EhPanda/App/ko.lproj/Localizable.strings index 3c0494ee..ce5b1e0f 100644 --- a/EhPanda/App/ko.lproj/Localizable.strings +++ b/EhPanda/App/ko.lproj/Localizable.strings @@ -50,6 +50,9 @@ "Jump page" = "페이지 이동"; "Confirm" = "확인"; +// MARK: HomeView +//"Clear history" = ""; + // MARK: DetailView "Archive" = "아카이브"; "Torrents" = "토렌트"; @@ -120,11 +123,9 @@ "Username" = "이름"; "Password" = "비밀번호"; "Logout" = "로그아웃"; +"Are you sure to logout?" = "로그아웃 하시겠어요?"; "Account configuration" = "계정 설정"; "Manage tags subscription" = "태그 구독 관리"; -"Are you sure to logout?" = "로그아웃 하시겠어요?"; -"Are you sure to clear?" = "삭제하시겠어요?"; -"Clear" = "삭제"; "Copy cookies" = "쿠키 복사하기"; "General" = "일반"; @@ -135,6 +136,8 @@ "Auto-Lock" = "앱 자동 잠금"; "App switcher blur" = "앱 안 쓸 때 자동으로 흐려지게 만들기"; "Cache" = "캐시"; +"Clear" = "삭제"; +"Are you sure to clear?" = "삭제하시겠어요?"; "Clear image caches" = "이미지 캐시 지우기"; "Appearance" = "외관"; diff --git a/EhPanda/App/zh-Hans.lproj/Localizable.strings b/EhPanda/App/zh-Hans.lproj/Localizable.strings index 87cd7a1a..d22ac730 100644 --- a/EhPanda/App/zh-Hans.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hans.lproj/Localizable.strings @@ -50,6 +50,9 @@ "Jump page" = "页码跳转"; "Confirm" = "确认"; +// MARK: HomeView +"Clear history" = "清空历史"; + // MARK: DetailView "Archive" = "归档"; "Torrents" = "种子"; @@ -120,11 +123,9 @@ "Username" = "用户名"; "Password" = "密码"; "Logout" = "退出登录"; +"Are you sure to logout?" = "确定要退出登录吗?"; "Account configuration" = "账户设置"; "Manage tags subscription" = "管理标签订阅"; -"Are you sure to logout?" = "确定要退出登录吗?"; -"Are you sure to clear?" = "确定要清空吗?"; -"Clear" = "清空"; "Copy cookies" = "复制 Cookies"; "General" = "一般"; @@ -135,7 +136,9 @@ "Auto-Lock" = "自动锁定"; "App switcher blur" = "在应用切换器中模糊处理"; "Cache" = "缓存"; +"Clear" = "清空"; "Clear image caches" = "清空图片缓存"; +"Are you sure to clear?" = "确定要清空吗?"; "Appearance" = "外观"; "Global" = "全局"; diff --git a/EhPanda/App/zh-Hant.lproj/Localizable.strings b/EhPanda/App/zh-Hant.lproj/Localizable.strings index d2fc5748..b4f5db51 100644 --- a/EhPanda/App/zh-Hant.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hant.lproj/Localizable.strings @@ -50,6 +50,9 @@ //"Jump page" = ""; //"Confirm" = ""; +// MARK: HomeView +"Clear history" = "清空歷史"; + // MARK: DetailView "Archive" = "封存"; "Torrents" = "種子"; @@ -120,11 +123,9 @@ //"Username" = ""; //"Password" = ""; "Logout" = "登出"; +"Are you sure to logout?" = "確定要登出嗎?"; "Account configuration" = "帳號設定"; "Manage tags subscription" = "管理訂閱標籤"; -"Are you sure to logout?" = "確定要登出嗎?"; -"Are you sure to clear?" = "確定要清空嗎?"; -"Clear" = "清空"; "Copy cookies" = "複製 Cookies"; "General" = "一般"; @@ -135,6 +136,8 @@ "Auto-Lock" = "自動鎖定"; "App switcher blur" = "模糊多工處理中的預覽"; "Cache" = "快取"; +"Clear" = "清空"; +"Are you sure to clear?" = "確定要清空嗎?"; "Clear image caches" = "清空圖片快取"; "Appearance" = "外觀"; diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index ac6bf1e3..6d6f815c 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -12,12 +12,12 @@ import Foundation enum AppAction { case resetUser case resetFilters + case resetHomeInfo case setReadingProgress(gid: String, tag: Int) - case setDiskImageCacheSize(size: String) case setAppIconType(_ iconType: IconType) - case appendHistoryKeywords(text: String) + case appendHistoryKeywords(texts: [String]) + case removeHistoryKeyword(text: String) case clearHistoryKeywords - case setLastKeyword(text: String) case setViewControllersCount case setSetting(_ setting: Setting) case setGalleryCommentJumpID(gid: String?) @@ -38,8 +38,6 @@ enum AppAction { case setNavigationBarHidden(_ hidden: Bool) case setHomeViewSheetState(_ state: HomeViewSheetState?) case setSettingViewSheetState(_ state: SettingViewSheetState?) - case setSettingViewActionSheetState(_ state: SettingViewActionSheetState) - case setFilterViewActionSheetState(_ state: FilterViewActionSheetState) case setDetailViewSheetState(_ state: DetailViewSheetState?) case setCommentViewSheetState(_ state: CommentViewSheetState?) diff --git a/EhPanda/DataFlow/AppState.swift b/EhPanda/DataFlow/AppState.swift index a522649c..9efae269 100644 --- a/EhPanda/DataFlow/AppState.swift +++ b/EhPanda/DataFlow/AppState.swift @@ -30,8 +30,6 @@ extension AppState { var homeListType: HomeListType = .frontpage var homeViewSheetState: HomeViewSheetState? var settingViewSheetState: SettingViewSheetState? - var settingViewActionSheetState: SettingViewActionSheetState? - var filterViewActionSheetState: FilterViewActionSheetState? var detailViewSheetState: DetailViewSheetState? var commentViewSheetState: CommentViewSheetState? @@ -96,8 +94,6 @@ extension AppState { extension AppState { // MARK: HomeInfo struct HomeInfo { - var lastKeyword = "" - var searchItems = [Gallery]() var searchLoading = false var searchLoadError: AppError? @@ -172,24 +168,32 @@ extension AppState { } } } - mutating func insertHistoryKeyword(text: String) { - guard !text.isEmpty else { return } - if let index = historyKeywords.firstIndex(of: text) { - if historyKeywords.last != text { - historyKeywords.remove(at: index) + mutating func appendHistoryKeywords(texts: [String]) { + guard !texts.isEmpty else { return } + var historyKeywords = historyKeywords + + texts.forEach { text in + guard !text.isEmpty else { return } + if let index = historyKeywords.firstIndex(of: text) { + if historyKeywords.last != text { + historyKeywords.remove(at: index) + historyKeywords.append(text) + } + } else { historyKeywords.append(text) - } - } else { - historyKeywords.append(text) - let overflow = historyKeywords.count - 10 - if overflow > 0 { - historyKeywords = Array( - historyKeywords.dropFirst(overflow) - ) + let overflow = historyKeywords.count - 15 + if overflow > 0 { + historyKeywords = Array( + historyKeywords.dropFirst(overflow) + ) + } } } self.historyKeywords = historyKeywords } + mutating func removeHistoryKeyword(text: String) { + historyKeywords = historyKeywords.filter { $0 != text } + } mutating func appendQuickSearchWord() { quickSearchWords.append(QuickSearchWord(content: "")) } diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index e2664b68..af9ef120 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -99,18 +99,20 @@ final class Store: ObservableObject { appState.settings.user = User() case .resetFilters: appState.settings.filter = Filter() + case .resetHomeInfo: + appState.homeInfo = AppState.HomeInfo() + dispatch(.setHomeListType(.frontpage)) + dispatch(.fetchFrontpageItems(pageNum: nil)) case .setReadingProgress(let gid, let tag): PersistenceController.update(gid: gid, readingProgress: tag) - case .setDiskImageCacheSize(let size): - appState.settings.setting.diskImageCacheSize = size case .setAppIconType(let iconType): appState.settings.setting.appIconType = iconType - case .appendHistoryKeywords(let text): - appState.homeInfo.insertHistoryKeyword(text: text) + case .appendHistoryKeywords(let texts): + appState.homeInfo.appendHistoryKeywords(texts: texts) + case .removeHistoryKeyword(let text): + appState.homeInfo.removeHistoryKeyword(text: text) case .clearHistoryKeywords: appState.homeInfo.historyKeywords = [] - case .setLastKeyword(let text): - appState.homeInfo.lastKeyword = text case .setSetting(let setting): appState.settings.setting = setting case .setViewControllersCount: @@ -140,7 +142,8 @@ final class Store: ObservableObject { appState.environment.isAppUnlocked = !activated case .setBlurEffect(let activated): withAnimation(.linear(duration: 0.1)) { - appState.environment.blurRadius = activated ? 10 : 0 + appState.environment.blurRadius = + activated ? appState.settings.setting.backgroundBlurRadius : 0 } case .setHomeListType(let type): appState.environment.homeListType = type @@ -156,10 +159,6 @@ final class Store: ObservableObject { case .setSettingViewSheetState(let state): if state != nil { HapticUtil.generateFeedback(style: .light) } appState.environment.settingViewSheetState = state - case .setSettingViewActionSheetState(let state): - appState.environment.settingViewActionSheetState = state - case .setFilterViewActionSheetState(let state): - appState.environment.filterViewActionSheetState = state case .setDetailViewSheetState(let state): if state != nil { HapticUtil.generateFeedback(style: .light) } appState.environment.detailViewSheetState = state diff --git a/EhPanda/DataFlow/StoreAccessor.swift b/EhPanda/DataFlow/StoreAccessor.swift index fe2bd58a..25ff5894 100644 --- a/EhPanda/DataFlow/StoreAccessor.swift +++ b/EhPanda/DataFlow/StoreAccessor.swift @@ -72,8 +72,11 @@ extension StoreAccessor { var accentColor: Color { setting.accentColor } - var allowsResignActiveBlur: Bool { - setting.allowsResignActiveBlur + var appIconType: IconType { + setting.appIconType + } + var backgroundBlurRadius: Double { + setting.backgroundBlurRadius } var autoLockPolicy: AutoLockPolicy { setting.autoLockPolicy diff --git a/EhPanda/Database/Persistence.swift b/EhPanda/Database/Persistence.swift index b0897a3a..b7cf7260 100644 --- a/EhPanda/Database/Persistence.swift +++ b/EhPanda/Database/Persistence.swift @@ -15,9 +15,8 @@ struct PersistenceController { let container = NSPersistentCloudKitContainer(name: "Model") container.loadPersistentStores { - if let error = $1 { - SwiftyBeaver.error(error as Any) - } + guard let error = $1 else { return } + SwiftyBeaver.error(error as Any) } return container }() @@ -30,13 +29,12 @@ struct PersistenceController { static func saveContext() { let context = shared.container.viewContext AppUtil.dispatchMainSync { - if context.hasChanges { - do { - try context.save() - } catch { - SwiftyBeaver.error(error) - fatalError("Unresolved error \(error)") - } + guard context.hasChanges else { return } + do { + try context.save() + } catch { + SwiftyBeaver.error(error) + fatalError("Unresolved error \(error)") } } } @@ -48,14 +46,11 @@ struct PersistenceController { } static func materializedObjects( - in context: NSManagedObjectContext, - matching predicate: NSPredicate + in context: NSManagedObjectContext, matching predicate: NSPredicate ) -> [NSManagedObject] { var objects = [NSManagedObject]() - for object in context.registeredObjects - where !object.isFault { - guard object.entity.attributesByName - .keys.contains("gid"), + for object in context.registeredObjects where !object.isFault { + guard object.entity.attributesByName.keys.contains("gid"), predicate.evaluate(with: object) else { continue } objects.append(object) @@ -67,7 +62,7 @@ struct PersistenceController { entityType: MO.Type, predicate: NSPredicate? = nil, findBeforeFetch: Bool = true, commitChanges: ((MO?) -> Void)? = nil ) -> MO? { - let managedObject = fetch( + let managedObject = batchFetch( entityType: entityType, fetchLimit: 1, predicate: predicate, findBeforeFetch: findBeforeFetch ).first @@ -75,12 +70,9 @@ struct PersistenceController { return managedObject } - static func fetch( - entityType: MO.Type, - fetchLimit: Int = 0, - predicate: NSPredicate? = nil, - findBeforeFetch: Bool = true, - sortDescriptors: [NSSortDescriptor]? = nil + static func batchFetch( + entityType: MO.Type, fetchLimit: Int = 0, predicate: NSPredicate? = nil, + findBeforeFetch: Bool = true, sortDescriptors: [NSSortDescriptor]? = nil ) -> [MO] { var results = [MO]() let context = shared.container.viewContext @@ -109,17 +101,11 @@ struct PersistenceController { commitChanges: ((MO?) -> Void)? = nil ) -> MO { if let storedMO = fetch( - entityType: entityType, - predicate: predicate, - commitChanges: commitChanges + entityType: entityType, predicate: predicate, commitChanges: commitChanges ) { return storedMO } else { - let newMO = MO( - context: shared - .container - .viewContext - ) + let newMO = MO(context: shared.container.viewContext) commitChanges?(newMO) saveContext() return newMO @@ -127,22 +113,14 @@ struct PersistenceController { } static func update( - entityType: MO.Type, - predicate: NSPredicate? = nil, - createIfNil: Bool = false, - commitChanges: ((MO) -> Void) + entityType: MO.Type, predicate: NSPredicate? = nil, + createIfNil: Bool = false, commitChanges: (MO) -> Void ) { let storedMO: MO? if createIfNil { - storedMO = fetchOrCreate( - entityType: entityType, - predicate: predicate - ) + storedMO = fetchOrCreate(entityType: entityType, predicate: predicate) } else { - storedMO = fetch( - entityType: entityType, - predicate: predicate - ) + storedMO = fetch(entityType: entityType, predicate: predicate) } if let storedMO = storedMO { commitChanges(storedMO) @@ -150,6 +128,14 @@ struct PersistenceController { } } + static func batchUpdate( + entityType: MO.Type, predicate: NSPredicate? = nil, commitChanges: ([MO]) -> Void + ) { + let storedMOs = batchFetch(entityType: entityType, predicate: predicate, findBeforeFetch: false) + commitChanges(storedMOs) + saveContext() + } + static func update( entityType: MO.Type, gid: String, createIfNil: Bool = false, @@ -158,13 +144,9 @@ struct PersistenceController { AppUtil.dispatchMainSync { let storedMO: MO? if createIfNil { - storedMO = fetchOrCreate( - entityType: entityType, gid: gid - ) + storedMO = fetchOrCreate(entityType: entityType, gid: gid) } else { - storedMO = fetch( - entityType: entityType, gid: gid - ) + storedMO = fetch(entityType: entityType, gid: gid) } if let storedMO = storedMO { commitChanges(storedMO) diff --git a/EhPanda/Database/PersistenceAccessor.swift b/EhPanda/Database/PersistenceAccessor.swift index 5bd9f279..efcc652e 100644 --- a/EhPanda/Database/PersistenceAccessor.swift +++ b/EhPanda/Database/PersistenceAccessor.swift @@ -62,13 +62,19 @@ extension PersistenceController { let sortDescriptor = NSSortDescriptor( keyPath: \GalleryMO.lastOpenDate, ascending: false ) - return fetch( - entityType: GalleryMO.self, - predicate: predicate, - findBeforeFetch: false, - sortDescriptors: [sortDescriptor] + return batchFetch( + entityType: GalleryMO.self, predicate: predicate, + findBeforeFetch: false, sortDescriptors: [sortDescriptor] ).map({ $0.toEntity() }) } + static func clearGalleryHistory() { + let predicate = NSPredicate(format: "lastOpenDate != nil") + batchUpdate(entityType: GalleryMO.self, predicate: predicate) { galleryMOs in + galleryMOs.forEach { galleryMO in + galleryMO.lastOpenDate = nil + } + } + } static func fetch( entityType: MO.Type, gid: String, @@ -76,22 +82,13 @@ extension PersistenceController { commitChanges: ((MO?) -> Void)? = nil ) -> MO? { fetch( - entityType: entityType, - predicate: NSPredicate( - format: "gid == %@", gid - ), - findBeforeFetch: findBeforeFetch, - commitChanges: commitChanges + entityType: entityType, predicate: NSPredicate(format: "gid == %@", gid), + findBeforeFetch: findBeforeFetch, commitChanges: commitChanges ) } - static func fetchOrCreate( - entityType: MO.Type, gid: String - ) -> MO { + static func fetchOrCreate(entityType: MO.Type, gid: String) -> MO { fetchOrCreate( - entityType: entityType, - predicate: NSPredicate( - format: "gid == %@", gid - ) + entityType: entityType, predicate: NSPredicate(format: "gid == %@", gid) ) { managedObject in managedObject?.gid = gid } @@ -99,14 +96,22 @@ extension PersistenceController { static func add(galleries: [Gallery]) { for gallery in galleries { - let storedMO = fetch( - entityType: GalleryMO.self, - gid: gallery.gid - ) { managedObject in - managedObject?.title = gallery.title - managedObject?.rating = gallery.rating - managedObject?.language = gallery.language?.rawValue + let storedMO = fetch(entityType: GalleryMO.self, gid: gallery.gid) { managedObject in + managedObject?.category = gallery.category.rawValue + managedObject?.coverURL = gallery.coverURL + managedObject?.galleryURL = gallery.galleryURL + if let language = gallery.language { + managedObject?.language = language.rawValue + } + // managedObject?.lastOpenDate = gallery.lastOpenDate managedObject?.pageCount = Int64(gallery.pageCount) + managedObject?.postedDate = gallery.postedDate + managedObject?.rating = gallery.rating + managedObject?.title = gallery.title + managedObject?.token = gallery.token + if let uploader = gallery.uploader { + managedObject?.uploader = uploader + } } if storedMO == nil { gallery.toManagedObject(in: shared.container.viewContext) @@ -116,24 +121,26 @@ extension PersistenceController { } static func add(detail: GalleryDetail) { - let storedMO = fetch( - entityType: GalleryDetailMO.self, - gid: detail.gid - ) { managedObject in - managedObject?.title = detail.title - managedObject?.jpnTitle = detail.jpnTitle + let storedMO = fetch(entityType: GalleryDetailMO.self, gid: detail.gid) { managedObject in + managedObject?.archiveURL = detail.archiveURL + managedObject?.category = detail.category.rawValue + managedObject?.coverURL = detail.coverURL managedObject?.isFavored = detail.isFavored managedObject?.visibility = detail.visibility.toData() + managedObject?.jpnTitle = detail.jpnTitle + managedObject?.language = detail.language.rawValue + managedObject?.favoredCount = Int64(detail.favoredCount) + managedObject?.pageCount = Int64(detail.pageCount) + managedObject?.parentURL = detail.parentURL + managedObject?.postedDate = detail.postedDate managedObject?.rating = detail.rating managedObject?.userRating = detail.userRating managedObject?.ratingCount = Int64(detail.ratingCount) - managedObject?.archiveURL = detail.archiveURL - managedObject?.parentURL = detail.parentURL - managedObject?.favoredCount = Int64(detail.favoredCount) - managedObject?.pageCount = Int64(detail.pageCount) managedObject?.sizeCount = detail.sizeCount managedObject?.sizeType = detail.sizeType + managedObject?.title = detail.title managedObject?.torrentCount = Int64(detail.torrentCount) + managedObject?.uploader = detail.uploader } if storedMO == nil { detail.toManagedObject(in: shared.container.viewContext) @@ -143,10 +150,7 @@ extension PersistenceController { static func galleryCached(gid: String) -> Bool { PersistenceController.checkExistence( - entityType: GalleryMO.self, - predicate: NSPredicate( - format: "gid == %@", gid - ) + entityType: GalleryMO.self, predicate: NSPredicate(format: "gid == %@", gid) ) } @@ -155,11 +159,20 @@ extension PersistenceController { galleryMO.lastOpenDate = Date() } } - static func update(appEnvMO: ((AppEnvMO) -> Void)) { + static func update(appEnvMO: (AppEnvMO) -> Void) { update(entityType: AppEnvMO.self, createIfNil: true, commitChanges: appEnvMO) } // MARK: GalleryState + static func removeImageURLs() { + batchUpdate(entityType: GalleryStateMO.self) { galleryStateMOs in + galleryStateMOs.forEach { galleryStateMO in + galleryStateMO.contents = nil + galleryStateMO.previews = nil + galleryStateMO.thumbnails = nil + } + } + } static func update(gid: String, galleryStateMO: @escaping ((GalleryStateMO) -> Void)) { update(entityType: GalleryStateMO.self, gid: gid, createIfNil: true, commitChanges: galleryStateMO) } @@ -170,27 +183,25 @@ extension PersistenceController { } static func update(gid: String, thumbnails: [Int: URL]) { update(gid: gid) { galleryStateMO in - if !thumbnails.isEmpty { - if let storedThumbnails = galleryStateMO.thumbnails?.toObject() as [Int: URL]? { - galleryStateMO.thumbnails = storedThumbnails.merging( - thumbnails, uniquingKeysWith: { _, new in new } - ).toData() - } else { - galleryStateMO.thumbnails = thumbnails.toData() - } + guard !thumbnails.isEmpty else { return } + if let storedThumbnails = galleryStateMO.thumbnails?.toObject() as [Int: URL]? { + galleryStateMO.thumbnails = storedThumbnails.merging( + thumbnails, uniquingKeysWith: { _, new in new } + ).toData() + } else { + galleryStateMO.thumbnails = thumbnails.toData() } } } static func update(gid: String, contents: [Int: String]) { update(gid: gid) { galleryStateMO in - if !contents.isEmpty { - if let storedContents = galleryStateMO.contents?.toObject() as [Int: String]? { - galleryStateMO.contents = storedContents.merging( - contents, uniquingKeysWith: { _, new in new } - ).toData() - } else { - galleryStateMO.contents = contents.toData() - } + guard !contents.isEmpty else { return } + if let storedContents = galleryStateMO.contents?.toObject() as [Int: String]? { + galleryStateMO.contents = storedContents.merging( + contents, uniquingKeysWith: { _, new in new } + ).toData() + } else { + galleryStateMO.contents = contents.toData() } } } diff --git a/EhPanda/Models/Models.swift b/EhPanda/Models/Models.swift index 7380da52..dde3e0e6 100644 --- a/EhPanda/Models/Models.swift +++ b/EhPanda/Models/Models.swift @@ -435,20 +435,17 @@ extension TranslatableLanguage { var repoName: String { switch self { case .japanese: - return Defaults.URL - .ehTagTranslationJpnRepo + return Defaults.URL.ehTagTranslationJpnRepo case .simplifiedChinese, .traditionalChinese: - return Defaults.URL - .ehTagTrasnlationRepo + return Defaults.URL.ehTagTrasnlationRepo } } var remoteFilename: String { switch self { case .japanese: return "jpn_text.json" - case .simplifiedChinese, - .traditionalChinese: + case .simplifiedChinese, .traditionalChinese: return "db.text.json" } } @@ -458,10 +455,7 @@ extension TranslatableLanguage { ) } var downloadLink: String { - Defaults.URL.githubDownload( - repoName: repoName, - fileName: remoteFilename - ) + Defaults.URL.githubDownload(repoName: repoName, fileName: remoteFilename) } } diff --git a/EhPanda/Models/Setting.swift b/EhPanda/Models/Setting.swift index 0cd343c5..25d47f8b 100644 --- a/EhPanda/Models/Setting.swift +++ b/EhPanda/Models/Setting.swift @@ -16,8 +16,7 @@ struct Setting: Codable { // General @DefaultFalse var redirectsLinksToSelectedHost = false @DefaultFalse var detectsLinksFromPasteboard = false - @DefaultStringValue var diskImageCacheSize = "0 KB" - @DefaultTrue var allowsResignActiveBlur = true + @DefaultDoubleValue var backgroundBlurRadius = 10 @DefaultAutoLockPolicy var autoLockPolicy: AutoLockPolicy = .never // Appearance diff --git a/EhPanda/View/Detail/AssociatedView.swift b/EhPanda/View/Detail/AssociatedView.swift index 2ad41ebe..03acb7ed 100644 --- a/EhPanda/View/Detail/AssociatedView.swift +++ b/EhPanda/View/Detail/AssociatedView.swift @@ -40,7 +40,9 @@ struct AssociatedView: View, StoreAccessor { moreLoadFailedFlag: moreLoadFailedFlag, fetchAction: fetchAssociatedItems, loadMoreAction: fetchMoreAssociatedItems, translateAction: translateTag ) - .searchable(text: $keyword, placement: .navigationBarDrawer(displayMode: .always)) + .searchable( + text: $keyword, placement: .navigationBarDrawer(displayMode: .always) + ) { SuggestionProvider(keyword: $keyword) } .toolbar(content: toolbar) .customAlert( manager: alertManager, widthFactor: DeviceUtil.isPadWidth ? 0.5 : 1.0, @@ -186,7 +188,7 @@ private extension AssociatedView { let token = SubscriptionToken() MoreSearchItemsRequest( - keyword: keyword, filter: filter, + keyword: keyword.isEmpty ? title : keyword, filter: filter, lastID: lastID, pageNum: pageNumber.current + 1 ) .publisher.receive(on: DispatchQueue.main) diff --git a/EhPanda/View/Detail/DetailView.swift b/EhPanda/View/Detail/DetailView.swift index bafa8cb5..deb887be 100644 --- a/EhPanda/View/Detail/DetailView.swift +++ b/EhPanda/View/Detail/DetailView.swift @@ -279,7 +279,7 @@ private struct HeaderView: View { var body: some View { HStack { KFImage(URL(string: gallery.coverURL)) - .placeholder { Placeholder(style: .activity(ratio: Defaults.ImageSize.headerScale)) } + .placeholder { Placeholder(style: .activity(ratio: Defaults.ImageSize.headerAspect)) } .defaultModifier().scaledToFit().frame(width: width, height: height) VStack(alignment: .leading) { Text(title).fontWeight(.bold).lineLimit(3).font(.title3) @@ -647,7 +647,7 @@ private struct PreviewView: View { Defaults.ImageSize.previewAvgW } private var height: CGFloat { - width / Defaults.ImageSize.previewScale + width / Defaults.ImageSize.previewAspect } var body: some View { @@ -679,7 +679,7 @@ private struct PreviewView: View { .placeholder { Placeholder(style: .activity( ratio: Defaults.ImageSize - .previewScale + .previewAspect )) } .imageModifier(modifier) @@ -745,7 +745,7 @@ private struct MorePreviewView: View { .placeholder { Placeholder(style: .activity( ratio: Defaults.ImageSize - .previewScale + .previewAspect )) } .imageModifier(modifier) diff --git a/EhPanda/View/Home/AuthView.swift b/EhPanda/View/Home/AuthView.swift index 7d5323a5..62bd1c65 100644 --- a/EhPanda/View/Home/AuthView.swift +++ b/EhPanda/View/Home/AuthView.swift @@ -41,7 +41,7 @@ private extension AuthView { lock() } func onResignActive(_: Any? = nil) { - guard allowsResignActiveBlur else { return } + guard backgroundBlurRadius > 0 else { return } setBlurEffect(activated: true) } func onDidBecomeActive(_: Any? = nil) { @@ -66,7 +66,7 @@ private extension AuthView { // MARK: Authorization func setBlurEffect(activated: Bool) { withAnimation(.linear(duration: 0.1)) { - blurRadius = activated ? 10 : 0 + blurRadius = activated ? backgroundBlurRadius : 0 } store.dispatch(.setBlurEffect(activated: activated)) } diff --git a/EhPanda/View/Home/FilterView.swift b/EhPanda/View/Home/FilterView.swift index 40687791..3a890477 100644 --- a/EhPanda/View/Home/FilterView.swift +++ b/EhPanda/View/Home/FilterView.swift @@ -9,6 +9,7 @@ import SwiftUI struct FilterView: View, StoreAccessor { @EnvironmentObject var store: Store + @State var resetDialogPresented = false private var categoryBindings: [Binding] { [ @@ -30,7 +31,7 @@ struct FilterView: View, StoreAccessor { Section { CategoryView(bindings: categoryBindings) Button { - store.dispatch(.setFilterViewActionSheetState(.resetFilters)) + resetDialogPresented = true } label: { Text("Reset filters").foregroundStyle(.red) } @@ -66,26 +67,18 @@ struct FilterView: View, StoreAccessor { } .disabled(!filter.advanced) } - .actionSheet(item: $store.appState.environment.filterViewActionSheetState, content: actionSheet) + .confirmationDialog( + "Are you sure to reset?", + isPresented: $resetDialogPresented, + titleVisibility: .visible + ) { + Button("Reset", role: .destructive) { + store.dispatch(.resetFilters) + } + } .navigationBarTitle("Filters") } } - - // MARK: ActionSheet - private func actionSheet(item: FilterViewActionSheetState) -> ActionSheet { - switch item { - case .resetFilters: - return ActionSheet( - title: Text("Are you sure to reset?"), - buttons: [ - .destructive(Text("Reset")) { - store.dispatch(.resetFilters) - }, - .cancel() - ] - ) - } - } } // MARK: MinimumRatingSetter @@ -152,12 +145,6 @@ private struct PagesRangeSetter: View { } // MARK: Definition -enum FilterViewActionSheetState: Identifiable { - var id: Int { hashValue } - - case resetFilters -} - private struct TupleCategory: Identifiable { var id: String { category.rawValue } diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index 3ae2ce52..a63805f1 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -13,7 +13,14 @@ struct HomeView: View, StoreAccessor { @EnvironmentObject var store: Store @Environment(\.colorScheme) private var colorScheme + @AppStorage(wrappedValue: .ehentai, AppUserDefaults.galleryHost.rawValue) + var galleryHost: GalleryHost + + @State private var isSearching = false @State private var keyword = "" + @State private var lastKeyword = "" + @State private var pendingKeywords = [String]() + @State private var clipboardJumpID: String? @State private var isNavLinkActive = false @State private var greeting: Greeting? @@ -24,12 +31,14 @@ struct HomeView: View, StoreAccessor { @State private var alertInput = "" @FocusState private var isAlertFocused: Bool @StateObject private var alertManager = CustomAlertManager() + @State private var clearHistoryDialogPresented = false // MARK: HomeView var body: some View { NavigationView { ZStack { conditionalList + SearchHelper(isSearching: $isSearching) TTProgressHUD($hudVisible, config: hudConfig) } .background { @@ -40,10 +49,8 @@ struct HomeView: View, StoreAccessor { ) } .searchable( - text: $keyword, - placement: .navigationBarDrawer(displayMode: .always), - suggestions: suggestions - ) + text: $keyword, placement: .navigationBarDrawer(displayMode: .always) + ) { SuggestionProvider(keyword: $keyword) } .navigationBarTitle(navigationBarTitle) .onSubmit(of: .search, performSearch) .toolbar(content: toolbar) @@ -59,8 +66,9 @@ struct HomeView: View, StoreAccessor { .onChange(of: environment.toplistsType) { _ in tryFetchToplistsItems() } .onChange(of: alertManager.isPresented) { _ in isAlertFocused = false } .onChange(of: environment.homeListType, perform: onHomeListTypeChange) + .onChange(of: galleryHost) { _ in store.dispatch(.resetHomeInfo) } .onChange(of: user.greeting, perform: tryPresentNewDawnSheet) - .onChange(of: keyword, perform: tryUpdateHistoryKeywords) + .onChange(of: isSearching, perform: tryUpdateHistoryKeywords) .customAlert( manager: alertManager, widthFactor: DeviceUtil.isPadWidth ? 0.5 : 1.0, backgroundOpacity: colorScheme == .light ? 0.2 : 0.5, @@ -72,26 +80,17 @@ struct HomeView: View, StoreAccessor { }, buttons: [.regular(content: { Text("Confirm") }, action: tryPerformJumpPage)] ) + .confirmationDialog( + "Are you sure to clear?", + isPresented: $clearHistoryDialogPresented, + titleVisibility: .visible + ) { + Button("Clear", role: .destructive, action: PersistenceController.clearGalleryHistory) + } } } private extension HomeView { - // MARK: Suggestions - @ViewBuilder - func suggestions() -> some View { - let historyKeywords = homeInfo.historyKeywords.reversed().filter({ word in - keyword.isEmpty ? true : word.contains(keyword) - }) - ForEach(historyKeywords, id: \.self) { word in - HStack { - Text(word).foregroundStyle(.tint) - Spacer() - } - .contentShape(Rectangle()) - .onTapGesture { keyword = word } - } - } - // MARK: Sheet func sheet(item: HomeViewSheetState) -> some View { Group { @@ -164,6 +163,15 @@ private extension HomeView { Text("Jump page") } .disabled(currentListTypePageNumber.isSinglePage) + if environment.homeListType == .history { + Button { + clearHistoryDialogPresented = true + } label: { + Image(systemName: "trash") + Text("Clear history") + } + .disabled(galleryHistory.isEmpty) + } } label: { Image(systemName: "ellipsis.circle") .symbolRenderingMode(.hierarchical) @@ -419,6 +427,16 @@ private extension HomeView { } } PasteboardUtil.clear() + clearObstruction() + } + } + // Removing this could cause unexpected blank leading space + func clearObstruction() { + if environment.homeViewSheetState != nil { + store.dispatch(.setHomeViewSheetState(nil)) + } + if !environment.slideMenuClosed { + NotificationUtil.post(.shouldHideSlideMenu) } } @@ -443,7 +461,7 @@ private extension HomeView { func tryPerformJumpPage() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { guard let index = Int(alertInput), index <= currentListTypePageNumber.maximum + 1 else { return } - store.dispatch(.handleJumpPage(index: index - 1, keyword: homeInfo.lastKeyword)) + store.dispatch(.handleJumpPage(index: index - 1, keyword: lastKeyword)) } } func tryActivateNavLink(newValue: String?) { @@ -454,20 +472,22 @@ private extension HomeView { } // MARK: Search - func tryUpdateHistoryKeywords(_ keyword: String) { - guard keyword.isEmpty else { return } - store.dispatch(.appendHistoryKeywords(text: homeInfo.lastKeyword)) + func tryUpdateHistoryKeywords(isSearching: Bool) { + guard !isSearching, !lastKeyword.isEmpty else { return } + store.dispatch(.appendHistoryKeywords(texts: pendingKeywords)) + pendingKeywords = [] } func tryRefetchSearchItems() { - guard !homeInfo.lastKeyword.isEmpty else { return } - store.dispatch(.fetchSearchItems(keyword: homeInfo.lastKeyword)) + guard !lastKeyword.isEmpty else { return } + store.dispatch(.fetchSearchItems(keyword: lastKeyword)) } func performSearch() { if environment.homeListType != .search { store.dispatch(.setHomeListType(.search)) } if !keyword.isEmpty { - store.dispatch(.setLastKeyword(text: keyword)) + pendingKeywords.append(keyword) + lastKeyword = keyword } store.dispatch(.fetchSearchItems(keyword: keyword)) } @@ -501,8 +521,8 @@ private extension HomeView { formatter.dateFormat = Defaults.DateFormat.greeting let currentTimeString = formatter.string(from: currentTime) - if let currDay = formatter.date(from: currentTimeString) { - return currentTime > currDay && updateTime < currDay + if let currentDay = formatter.date(from: currentTimeString) { + return currentTime > currentDay && updateTime < currentDay } return false @@ -535,7 +555,7 @@ private extension HomeView { } func fetchMoreSearchItems() { - store.dispatch(.fetchMoreSearchItems(keyword: homeInfo.lastKeyword)) + store.dispatch(.fetchMoreSearchItems(keyword: lastKeyword)) } func fetchMoreFrontpageItems() { store.dispatch(.fetchMoreFrontpageItems) @@ -564,6 +584,22 @@ private extension HomeView { } } +// MARK: SearchHelper +private struct SearchHelper: View { + @Environment(\.isSearching) var isSearchingEnvironment + @Binding var isSearching: Bool + + init(isSearching: Binding) { + _isSearching = isSearching + } + + var body: some View { + Text("").onChange(of: isSearchingEnvironment) { newValue in + isSearching = newValue + } + } +} + // MARK: Definition enum HomeListType: String, Identifiable, CaseIterable { var id: Int { hashValue } diff --git a/EhPanda/View/Home/SlideMenu.swift b/EhPanda/View/Home/SlideMenu.swift index 9d99b999..36552463 100644 --- a/EhPanda/View/Home/SlideMenu.swift +++ b/EhPanda/View/Home/SlideMenu.swift @@ -94,21 +94,17 @@ private struct AvatarView: View { self.height = height } - private func getPlaceholder() -> some View { - Image(iconName).resizable() - } - var body: some View { HStack { VStack(alignment: .leading) { Group { if avatarURL?.contains(".gif") != true { KFImage(URL(string: avatarURL ?? "")) - .placeholder(getPlaceholder).retry(maxCount: 10) + .placeholder(placeholder).retry(maxCount: 10) .defaultModifier(withRoundedCorners: false) } else { KFAnimatedImage(URL(string: avatarURL ?? "")) - .placeholder(getPlaceholder) + .placeholder(placeholder) // .fade(duration: 0.25) .retry(maxCount: 10) } @@ -123,6 +119,9 @@ private struct AvatarView: View { Spacer() } } + private func placeholder() -> some View { + Image(iconName).resizable() + } } // MARK: MenuRow diff --git a/EhPanda/View/Reading/ControlPanel.swift b/EhPanda/View/Reading/ControlPanel.swift index 4f45de42..cf91e8f1 100644 --- a/EhPanda/View/Reading/ControlPanel.swift +++ b/EhPanda/View/Reading/ControlPanel.swift @@ -269,7 +269,7 @@ private struct SliderPreivew: View { KFImage.url(URL(string: url), cacheKey: previews[index]) .placeholder { Placeholder(style: .activity( - ratio: Defaults.ImageSize.previewScale + ratio: Defaults.ImageSize.previewAspect )) } // .fade(duration: 0.25) @@ -309,7 +309,7 @@ private extension SliderPreivew { } var previewSpacing: CGFloat { 10 } var previewHeight: CGFloat { - previewWidth / Defaults.ImageSize.previewScale + previewWidth / Defaults.ImageSize.previewAspect } var previewWidth: CGFloat { guard previewsCount > 0 else { return 0 } diff --git a/EhPanda/View/Reading/ReadingView.swift b/EhPanda/View/Reading/ReadingView.swift index 9f2336ff..d77e7372 100644 --- a/EhPanda/View/Reading/ReadingView.swift +++ b/EhPanda/View/Reading/ReadingView.swift @@ -609,7 +609,7 @@ private struct ImageContainer: View { DeviceUtil.windowW / (isDualPage ? 2 : 1) } private var height: CGFloat { - width / Defaults.ImageSize.contentScale + width / Defaults.ImageSize.contentAspect } private var loadFailedFlag: Bool { loadError != nil || webImageLoadFailed diff --git a/EhPanda/View/Setting/AccountSettingView.swift b/EhPanda/View/Setting/AccountSettingView.swift index 027da83e..a25abd27 100644 --- a/EhPanda/View/Setting/AccountSettingView.swift +++ b/EhPanda/View/Setting/AccountSettingView.swift @@ -6,12 +6,14 @@ // import SwiftUI +import Kingfisher import TTProgressHUD struct AccountSettingView: View, StoreAccessor { @AppStorage(wrappedValue: .ehentai, AppUserDefaults.galleryHost.rawValue) var galleryHost: GalleryHost @EnvironmentObject var store: Store + @State private var logoutDialogPresented = false @State private var hudVisible = false @State private var hudConfig = TTProgressHUDConfig() @@ -40,7 +42,7 @@ struct AccountSettingView: View, StoreAccessor { NavigationLink("Login", destination: LoginView()).foregroundStyle(.tint) } else { Button("Logout", role: .destructive) { - store.dispatch(.setSettingViewActionSheetState(.logout)) + logoutDialogPresented = true } } if AuthorizationUtil.didLogin { @@ -73,12 +75,27 @@ struct AccountSettingView: View, StoreAccessor { } TTProgressHUD($hudVisible, config: hudConfig) } + .confirmationDialog( + "Are you sure to logout?", + isPresented: $logoutDialogPresented, + titleVisibility: .visible + ) { + Button("Logout", role: .destructive, action: logout) + } .navigationBarTitle("Account") } } private extension AccountSettingView { - // MARK: Cookies stuff + // MARK: Logout + func logout() { + CookiesUtil.clearAll() + store.dispatch(.resetUser) + PersistenceController.removeImageURLs() + KingfisherManager.shared.cache.clearDiskCache() + } + + // MARK: Cookies var igneous: CookieValue { CookiesUtil.get(for: exURL, key: igneousKey) } diff --git a/EhPanda/View/Setting/EhSettingView.swift b/EhPanda/View/Setting/EhSettingView.swift index 3f29c11e..cf093a09 100644 --- a/EhPanda/View/Setting/EhSettingView.swift +++ b/EhPanda/View/Setting/EhSettingView.swift @@ -39,7 +39,9 @@ struct EhSettingView: View, StoreAccessor { fetchEhSetting() } .onDisappear { - guard let set = ehSetting?.ehProfiles.filter({ $0.name == "EhPanda"}).first?.value else { return } + guard let set = ehSetting?.ehProfiles.filter({ + AppUtil.verifyEhPandaProfileName(with: $0.name) + }).first?.value else { return } CookiesUtil.set(for: Defaults.URL.host.safeURL(), key: Defaults.Cookie.selectedProfile, value: String(set)) } .toolbar(content: toolbar).navigationTitle(title) diff --git a/EhPanda/View/Setting/GeneralSettingView.swift b/EhPanda/View/Setting/GeneralSettingView.swift index 14d20b9f..78d2433a 100644 --- a/EhPanda/View/Setting/GeneralSettingView.swift +++ b/EhPanda/View/Setting/GeneralSettingView.swift @@ -7,11 +7,14 @@ import SwiftUI import Kingfisher +import SwiftyBeaver import LocalAuthentication struct GeneralSettingView: View, StoreAccessor { @EnvironmentObject var store: Store @State private var passcodeNotSet = false + @State private var diskImageCacheSize = "0 KB" + @State private var clearDialogPresented = false private var isTranslatesTagsVisible: Bool { guard let preferredLanguage = Locale.preferredLanguages.first else { return false } @@ -56,26 +59,38 @@ struct GeneralSettingView: View, StoreAccessor { } .pickerStyle(.menu) } - Toggle("App switcher blur", isOn: settingBinding.allowsResignActiveBlur) + VStack(alignment: .leading) { + Text("App switcher blur") + HStack { + Image(systemName: "eye") + Slider(value: settingBinding.backgroundBlurRadius, in: 0...100, step: 10) + Image(systemName: "eye.slash") + } + } } Section("Cache".localized) { Button { - store.dispatch(.setSettingViewActionSheetState(.clearImgCaches)) + clearDialogPresented = true } label: { HStack { Text("Clear image caches") Spacer() - Text(setting.diskImageCacheSize).foregroundStyle(.tint) + Text(diskImageCacheSize).foregroundStyle(.tint) } .foregroundColor(.primary) } } } - .onAppear(perform: checkPasscodeExistence) - .navigationBarTitle("General") + .confirmationDialog( + "Are you sure to clear?", + isPresented: $clearDialogPresented, + titleVisibility: .visible + ) { + Button("Clear", role: .destructive, action: clearImageCaches) + } + .onAppear(perform: onStartTasks).navigationBarTitle("General") } } - private extension GeneralSettingView { var settingBinding: Binding { $store.appState.settings.setting @@ -84,6 +99,10 @@ private extension GeneralSettingView { Locale.current.localizedString(forLanguageCode: Locale.current.languageCode ?? "") ?? "(null)" } + func onStartTasks() { + checkPasscodeExistence() + calculateDiskCachesSize() + } func checkPasscodeExistence() { var error: NSError? @@ -95,4 +114,25 @@ private extension GeneralSettingView { guard let settingURL = URL(string: UIApplication.openSettingsURLString) else { return } UIApplication.shared.open(settingURL, options: [:]) } + + func readableUnit(bytes: I) -> String { + let formatter = ByteCountFormatter() + formatter.allowedUnits = [.useAll] + return formatter.string(fromByteCount: Int64(bytes)) + } + func calculateDiskCachesSize() { + KingfisherManager.shared.cache.calculateDiskStorageSize { result in + switch result { + case .success(let size): + diskImageCacheSize = readableUnit(bytes: size) + case .failure(let error): + SwiftyBeaver.error(error) + } + } + } + func clearImageCaches() { + KingfisherManager.shared.cache.clearDiskCache() + PersistenceController.removeImageURLs() + calculateDiskCachesSize() + } } diff --git a/EhPanda/View/Setting/SettingView.swift b/EhPanda/View/Setting/SettingView.swift index 9a99e973..ba2517b6 100644 --- a/EhPanda/View/Setting/SettingView.swift +++ b/EhPanda/View/Setting/SettingView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import Kingfisher import SwiftyBeaver struct SettingView: View, StoreAccessor { @@ -24,7 +23,6 @@ struct SettingView: View, StoreAccessor { SettingRow( symbolName: "switch.2", text: "General", destination: GeneralSettingView() - .onAppear(perform: calculateDiskCachesSize) ) SettingRow( symbolName: "circle.righthalf.fill", text: "Appearance", @@ -46,8 +44,7 @@ struct SettingView: View, StoreAccessor { .padding(.vertical, 40).padding(.horizontal) } .navigationBarTitle("Setting") - .sheet(item: environmentBinding.settingViewSheetState, content: sheet) - .actionSheet(item: environmentBinding.settingViewActionSheetState, content: actionSheet) + .sheet(item: $store.appState.environment.settingViewSheetState, content: sheet) } } private func sheet(item: SettingViewSheetState) -> some View { @@ -64,50 +61,6 @@ struct SettingView: View, StoreAccessor { .blur(radius: environment.blurRadius) .allowsHitTesting(environment.isAppUnlocked) } - private func actionSheet(item: SettingViewActionSheetState) -> ActionSheet { - switch item { - case .logout: - return ActionSheet(title: Text("Are you sure to logout?"), buttons: [ - .destructive(Text("Logout"), action: logout), .cancel() - ]) - case .clearImgCaches: - return ActionSheet(title: Text("Are you sure to clear?"), buttons: [ - .destructive(Text("Clear"), action: clearImageCaches), .cancel() - ]) - } - } -} - -private extension SettingView { - var environmentBinding: Binding { - $store.appState.environment - } - - func logout() { - clearImageCaches() - CookiesUtil.clearAll() - store.dispatch(.resetUser) - } - - func readableUnit(bytes: I) -> String { - let formatter = ByteCountFormatter() - formatter.allowedUnits = [.useAll] - return formatter.string(fromByteCount: Int64(bytes)) - } - func calculateDiskCachesSize() { - KingfisherManager.shared.cache.calculateDiskStorageSize { result in - switch result { - case .success(let size): - store.dispatch(.setDiskImageCacheSize(size: readableUnit(bytes: size))) - case .failure(let error): - SwiftyBeaver.error(error) - } - } - } - func clearImageCaches() { - KingfisherManager.shared.cache.clearDiskCache() - calculateDiskCachesSize() - } } // MARK: SettingRow @@ -156,13 +109,6 @@ private struct SettingRow: View { } // MARK: Definition -enum SettingViewActionSheetState: Identifiable { - var id: Int { hashValue } - - case logout - case clearImgCaches -} - enum SettingViewSheetState: Identifiable { var id: Int { hashValue } diff --git a/EhPanda/View/Setting/WebView.swift b/EhPanda/View/Setting/WebView.swift index 23f68de3..51570303 100644 --- a/EhPanda/View/Setting/WebView.swift +++ b/EhPanda/View/Setting/WebView.swift @@ -10,9 +10,7 @@ import SwiftUI import SwiftyBeaver struct WebView: UIViewControllerRepresentable { - static let loginURLString - = "https://forums.e-hentai.org/" - + "index.php?act=Login" + static let loginURLString = "https://forums.e-hentai.org/index.php?act=Login" @EnvironmentObject private var store: Store private let url: URL @@ -41,8 +39,8 @@ struct WebView: UIViewControllerRepresentable { guard AuthorizationUtil.didLogin else { return } let store = self?.parent.store store?.dispatch(.setSettingViewSheetState(nil)) - store?.dispatch(.fetchFrontpageItems()) store?.dispatch(.verifyEhProfile) + store?.dispatch(.resetHomeInfo) store?.dispatch(.fetchUserInfo) } } diff --git a/EhPanda/View/Tools/GalleryDetailCell.swift b/EhPanda/View/Tools/GalleryDetailCell.swift index 5276d1f7..0fa44302 100644 --- a/EhPanda/View/Tools/GalleryDetailCell.swift +++ b/EhPanda/View/Tools/GalleryDetailCell.swift @@ -24,7 +24,7 @@ struct GalleryDetailCell: View { var body: some View { HStack(spacing: 10) { KFImage(URL(string: gallery.coverURL)) - .placeholder { Placeholder(style: .activity(ratio: Defaults.ImageSize.rowScale)) } + .placeholder { Placeholder(style: .activity(ratio: Defaults.ImageSize.rowAspect)) } .defaultModifier().scaledToFit().frame(width: Defaults.ImageSize.rowW, height: Defaults.ImageSize.rowH) VStack(alignment: .leading) { Text(gallery.title).lineLimit(1).font(.headline).foregroundStyle(.primary) diff --git a/EhPanda/View/Tools/GalleryThumbnailCell.swift b/EhPanda/View/Tools/GalleryThumbnailCell.swift index bc5dd67c..b81091eb 100644 --- a/EhPanda/View/Tools/GalleryThumbnailCell.swift +++ b/EhPanda/View/Tools/GalleryThumbnailCell.swift @@ -24,8 +24,12 @@ struct GalleryThumbnailCell: View { var body: some View { VStack(alignment: .leading, spacing: 0) { KFImage(URL(string: gallery.coverURL)) - .placeholder { Placeholder(style: .activity(ratio: Defaults.ImageSize.rowScale)) } -// .fade(duration: 0.25) + .placeholder { Placeholder(style: .activity(ratio: Defaults.ImageSize.rowAspect)) } + .imageModifier(WebtoonModifier( + minAspect: Defaults.ImageSize.webtoonMinAspect, + idealAspect: Defaults.ImageSize.webtoonIdealAspect + )) + /*.fade(duration: 0.25)*/ .resizable().scaledToFit().overlay { VStack { HStack { diff --git a/EhPanda/View/Tools/SuggestionProvider.swift b/EhPanda/View/Tools/SuggestionProvider.swift new file mode 100644 index 00000000..f31e6e2a --- /dev/null +++ b/EhPanda/View/Tools/SuggestionProvider.swift @@ -0,0 +1,38 @@ +// +// SuggestionProvider.swift +// EhPanda +// +// Created by 荒木辰造 on R 3/11/23. +// + +import SwiftUI + +struct SuggestionProvider: View { + @EnvironmentObject var store: Store + @Binding private var keyword: String + + init(keyword: Binding) { + _keyword = keyword + } + + private var keywords: [String] { + store.appState.homeInfo.historyKeywords.reversed().filter({ word in + keyword.isEmpty ? true : word.contains(keyword) + }) + } + + var body: some View { + ForEach(keywords, id: \.self) { word in + HStack { + Text(word).foregroundStyle(.tint) + Spacer() + Image(systemName: "xmark").imageScale(.small) + .foregroundColor(.secondary).onTapGesture { + store.dispatch(.removeHistoryKeyword(text: word)) + } + } + .contentShape(Rectangle()) + .onTapGesture { keyword = word } + } + } +}