From 34fa6c218c9c08f4d68a26869e8fbe10dae9e081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Mon, 22 Nov 2021 10:31:11 +0900 Subject: [PATCH 01/14] fix: Uploader `(Disowned)` parsing issue --- EhPanda/App/Tools/Parser.swift | 33 +++++++- EhPanda/Database/Persistence.swift | 68 +++++----------- EhPanda/Database/PersistenceAccessor.swift | 91 ++++++++++------------ 3 files changed, 93 insertions(+), 99 deletions(-) diff --git a/EhPanda/App/Tools/Parser.swift b/EhPanda/App/Tools/Parser.swift index ec131e4a..0c962850 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -93,12 +93,25 @@ 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 if let divText = divNode.text { + return divText + } else { + throw AppError.parseFailed + } + } + 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 guard let gl2cNode = link.at_xpath("//td [@class='gl2c']"), let gl3cNode = link.at_xpath("//td [@class='gl3c glname']"), + let uploader = try? parseUploader(node: link), let (rating, _, _) = try? parseRating(node: gl2cNode), let coverURL = try? parseCoverURL(node: gl2cNode), let pageCount = try? parsePageCount(node: gl2cNode), @@ -303,6 +316,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 +351,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 ?? ""), diff --git a/EhPanda/Database/Persistence.swift b/EhPanda/Database/Persistence.swift index b0897a3a..15f1df00 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) @@ -76,11 +71,8 @@ struct PersistenceController { } static func fetch( - entityType: MO.Type, - fetchLimit: Int = 0, - predicate: NSPredicate? = nil, - findBeforeFetch: Bool = true, - sortDescriptors: [NSSortDescriptor]? = nil + 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) @@ -158,13 +136,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..7a63c618 100644 --- a/EhPanda/Database/PersistenceAccessor.swift +++ b/EhPanda/Database/PersistenceAccessor.swift @@ -63,10 +63,8 @@ extension PersistenceController { keyPath: \GalleryMO.lastOpenDate, ascending: false ) return fetch( - entityType: GalleryMO.self, - predicate: predicate, - findBeforeFetch: false, - sortDescriptors: [sortDescriptor] + entityType: GalleryMO.self, predicate: predicate, + findBeforeFetch: false, sortDescriptors: [sortDescriptor] ).map({ $0.toEntity() }) } @@ -76,22 +74,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 +88,18 @@ 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 + let storedMO = fetch(entityType: GalleryMO.self, gid: gallery.gid) { managedObject in + managedObject?.category = gallery.category.rawValue + managedObject?.coverURL = gallery.coverURL + managedObject?.galleryURL = gallery.galleryURL managedObject?.language = gallery.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 + managedObject?.uploader = gallery.uploader } if storedMO == nil { gallery.toManagedObject(in: shared.container.viewContext) @@ -120,20 +113,25 @@ extension PersistenceController { entityType: GalleryDetailMO.self, gid: detail.gid ) { managedObject in - managedObject?.title = detail.title - managedObject?.jpnTitle = detail.jpnTitle + 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 +141,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) ) } @@ -170,27 +165,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() } } } From bd6f66e3dc099614f32c9a3b49a88dda07e69fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Mon, 22 Nov 2021 10:31:36 +0900 Subject: [PATCH 02/14] fix: Restore `clearObstruction` function --- EhPanda/View/Home/HomeView.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index 3ae2ce52..31769267 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -419,6 +419,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) } } @@ -501,8 +511,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 From 696b9ae95c934549a1f418e0eac090c6d66ae303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Mon, 22 Nov 2021 10:39:22 +0900 Subject: [PATCH 03/14] fix: Parsing & Database --- EhPanda/App/Tools/Parser.swift | 8 +++----- EhPanda/Database/PersistenceAccessor.swift | 10 +++++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/EhPanda/App/Tools/Parser.swift b/EhPanda/App/Tools/Parser.swift index 0c962850..7e46508f 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -93,25 +93,23 @@ struct Parser { throw AppError.parseFailed } - func parseUploader(node: XMLElement?) throws -> String { + 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 if let divText = divNode.text { - return divText } else { - throw AppError.parseFailed + return divNode.text } } var galleryItems = [Gallery]() for link in doc.xpath("//tr") { + 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 uploader = try? parseUploader(node: link), let (rating, _, _) = try? parseRating(node: gl2cNode), let coverURL = try? parseCoverURL(node: gl2cNode), let pageCount = try? parsePageCount(node: gl2cNode), diff --git a/EhPanda/Database/PersistenceAccessor.swift b/EhPanda/Database/PersistenceAccessor.swift index 7a63c618..ef669ee1 100644 --- a/EhPanda/Database/PersistenceAccessor.swift +++ b/EhPanda/Database/PersistenceAccessor.swift @@ -92,14 +92,18 @@ extension PersistenceController { managedObject?.category = gallery.category.rawValue managedObject?.coverURL = gallery.coverURL managedObject?.galleryURL = gallery.galleryURL - managedObject?.language = gallery.language?.rawValue - managedObject?.lastOpenDate = gallery.lastOpenDate + 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 - managedObject?.uploader = gallery.uploader + if let uploader = gallery.uploader { + managedObject?.uploader = uploader + } } if storedMO == nil { gallery.toManagedObject(in: shared.container.viewContext) From 7e505444b1caabf9b7e661431abffd7687f28404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Mon, 22 Nov 2021 11:05:47 +0900 Subject: [PATCH 04/14] fix: Creating multiple EhPanda profiles issue --- EhPanda/App/Tools/Parser.swift | 9 +++------ EhPanda/App/Utilities.swift | 4 ++++ EhPanda/View/Setting/EhSettingView.swift | 4 +++- EhPanda/View/Setting/WebView.swift | 4 +--- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/EhPanda/App/Tools/Parser.swift b/EhPanda/App/Tools/Parser.swift index 7e46508f..e4891414 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -1309,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"] ?? "") } diff --git a/EhPanda/App/Utilities.swift b/EhPanda/App/Utilities.swift index 783215f1..9f0df480 100644 --- a/EhPanda/App/Utilities.swift +++ b/EhPanda/App/Utilities.swift @@ -77,6 +77,10 @@ struct AppUtil { UserDefaultsUtil.set(value: value.rawValue, forKey: .galleryHost) } + static func verifyEhPandaProfileName(with name: 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/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/WebView.swift b/EhPanda/View/Setting/WebView.swift index 23f68de3..631f08dc 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 From 78e0d8e68a2476c3699592b2262a935eb308689f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 11:19:07 +0900 Subject: [PATCH 05/14] Update dependencies --- EhPanda.xcodeproj/project.pbxproj | 120 +++++++++--------- .../xcshareddata/swiftpm/Package.resolved | 18 +-- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/EhPanda.xcodeproj/project.pbxproj b/EhPanda.xcodeproj/project.pbxproj index 9b6fb293..ad87805d 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,14 @@ 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 */; }; 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 +60,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 +79,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 */; }; @@ -298,13 +298,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; }; @@ -673,15 +673,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 +742,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 = ""; @@ -1318,48 +1318,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 +1378,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 +1399,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 } }, From 7a2ad3861ce16c5e7000c9b67433e8a6d563549d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 11:20:05 +0900 Subject: [PATCH 06/14] fix: Webtoon thumbnail mode cover issue --- EhPanda/Models/Models.swift | 14 ++++---------- EhPanda/View/Tools/GalleryThumbnailCell.swift | 4 ++-- 2 files changed, 6 insertions(+), 12 deletions(-) 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/View/Tools/GalleryThumbnailCell.swift b/EhPanda/View/Tools/GalleryThumbnailCell.swift index bc5dd67c..e2b7e32c 100644 --- a/EhPanda/View/Tools/GalleryThumbnailCell.swift +++ b/EhPanda/View/Tools/GalleryThumbnailCell.swift @@ -25,8 +25,8 @@ struct GalleryThumbnailCell: View { VStack(alignment: .leading, spacing: 0) { KFImage(URL(string: gallery.coverURL)) .placeholder { Placeholder(style: .activity(ratio: Defaults.ImageSize.rowScale)) } -// .fade(duration: 0.25) - .resizable().scaledToFit().overlay { + /*.fade(duration: 0.25)*/ + .resizable().frame(maxHeight: DeviceUtil.absWindowH * 2/3).scaledToFit().overlay { VStack { HStack { Spacer() From 3de2f78b224c84f17208e2e942de6d957cdd6ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 12:28:13 +0900 Subject: [PATCH 07/14] feat: Erase database records with image caches #175 --- EhPanda/Database/Persistence.swift | 12 +++++++++--- EhPanda/Database/PersistenceAccessor.swift | 16 +++++++++++----- EhPanda/View/Setting/GeneralSettingView.swift | 2 +- EhPanda/View/Setting/SettingView.swift | 5 +++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/EhPanda/Database/Persistence.swift b/EhPanda/Database/Persistence.swift index 15f1df00..9810e6fa 100644 --- a/EhPanda/Database/Persistence.swift +++ b/EhPanda/Database/Persistence.swift @@ -62,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 @@ -70,7 +70,7 @@ struct PersistenceController { return managedObject } - static func fetch( + static func batchFetch( entityType: MO.Type, fetchLimit: Int = 0, predicate: NSPredicate? = nil, findBeforeFetch: Bool = true, sortDescriptors: [NSSortDescriptor]? = nil ) -> [MO] { @@ -114,7 +114,7 @@ struct PersistenceController { static func update( entityType: MO.Type, predicate: NSPredicate? = nil, - createIfNil: Bool = false, commitChanges: ((MO) -> Void) + createIfNil: Bool = false, commitChanges: (MO) -> Void ) { let storedMO: MO? if createIfNil { @@ -128,6 +128,12 @@ struct PersistenceController { } } + static func batchUpdate(entityType: MO.Type, commitChanges: ([MO]) -> Void) { + let storedMOs = batchFetch(entityType: entityType) + commitChanges(storedMOs) + saveContext() + } + static func update( entityType: MO.Type, gid: String, createIfNil: Bool = false, diff --git a/EhPanda/Database/PersistenceAccessor.swift b/EhPanda/Database/PersistenceAccessor.swift index ef669ee1..dab70c20 100644 --- a/EhPanda/Database/PersistenceAccessor.swift +++ b/EhPanda/Database/PersistenceAccessor.swift @@ -62,7 +62,7 @@ extension PersistenceController { let sortDescriptor = NSSortDescriptor( keyPath: \GalleryMO.lastOpenDate, ascending: false ) - return fetch( + return batchFetch( entityType: GalleryMO.self, predicate: predicate, findBeforeFetch: false, sortDescriptors: [sortDescriptor] ).map({ $0.toEntity() }) @@ -113,10 +113,7 @@ extension PersistenceController { } static func add(detail: GalleryDetail) { - let storedMO = fetch( - entityType: GalleryDetailMO.self, - gid: detail.gid - ) { managedObject in + let storedMO = fetch(entityType: GalleryDetailMO.self, gid: detail.gid) { managedObject in managedObject?.archiveURL = detail.archiveURL managedObject?.category = detail.category.rawValue managedObject?.coverURL = detail.coverURL @@ -159,6 +156,15 @@ extension PersistenceController { } // 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) } diff --git a/EhPanda/View/Setting/GeneralSettingView.swift b/EhPanda/View/Setting/GeneralSettingView.swift index 14d20b9f..a6aafef0 100644 --- a/EhPanda/View/Setting/GeneralSettingView.swift +++ b/EhPanda/View/Setting/GeneralSettingView.swift @@ -60,7 +60,7 @@ struct GeneralSettingView: View, StoreAccessor { } Section("Cache".localized) { Button { - store.dispatch(.setSettingViewActionSheetState(.clearImgCaches)) + store.dispatch(.setSettingViewActionSheetState(.clearImageCaches)) } label: { HStack { Text("Clear image caches") diff --git a/EhPanda/View/Setting/SettingView.swift b/EhPanda/View/Setting/SettingView.swift index 9a99e973..b233f33d 100644 --- a/EhPanda/View/Setting/SettingView.swift +++ b/EhPanda/View/Setting/SettingView.swift @@ -70,7 +70,7 @@ struct SettingView: View, StoreAccessor { return ActionSheet(title: Text("Are you sure to logout?"), buttons: [ .destructive(Text("Logout"), action: logout), .cancel() ]) - case .clearImgCaches: + case .clearImageCaches: return ActionSheet(title: Text("Are you sure to clear?"), buttons: [ .destructive(Text("Clear"), action: clearImageCaches), .cancel() ]) @@ -106,6 +106,7 @@ private extension SettingView { } func clearImageCaches() { KingfisherManager.shared.cache.clearDiskCache() + PersistenceController.removeImageURLs() calculateDiskCachesSize() } } @@ -160,7 +161,7 @@ enum SettingViewActionSheetState: Identifiable { var id: Int { hashValue } case logout - case clearImgCaches + case clearImageCaches } enum SettingViewSheetState: Identifiable { From 8fd6ef2a87bbcb808431632d55ade1951dd85f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 13:35:52 +0900 Subject: [PATCH 08/14] feat: Blur radius configuration #168 --- EhPanda/DataFlow/Store.swift | 3 ++- EhPanda/DataFlow/StoreAccessor.swift | 7 +++++-- EhPanda/Models/Setting.swift | 2 +- EhPanda/View/Home/AuthView.swift | 4 ++-- EhPanda/View/Home/SlideMenu.swift | 11 +++++------ EhPanda/View/Setting/GeneralSettingView.swift | 9 ++++++++- 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index e2664b68..1733ed38 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -140,7 +140,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 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/Models/Setting.swift b/EhPanda/Models/Setting.swift index 0cd343c5..45748ec2 100644 --- a/EhPanda/Models/Setting.swift +++ b/EhPanda/Models/Setting.swift @@ -17,7 +17,7 @@ struct Setting: Codable { @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/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/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/Setting/GeneralSettingView.swift b/EhPanda/View/Setting/GeneralSettingView.swift index a6aafef0..944f274f 100644 --- a/EhPanda/View/Setting/GeneralSettingView.swift +++ b/EhPanda/View/Setting/GeneralSettingView.swift @@ -56,7 +56,14 @@ 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 { From bee4159ea9db79c2c21ffba181e2f5744fcc60e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 17:21:16 +0900 Subject: [PATCH 09/14] fix: Use `title` instead when `keyword` is empty in similar gallery #177 --- EhPanda/View/Detail/AssociatedView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EhPanda/View/Detail/AssociatedView.swift b/EhPanda/View/Detail/AssociatedView.swift index 2ad41ebe..1256928d 100644 --- a/EhPanda/View/Detail/AssociatedView.swift +++ b/EhPanda/View/Detail/AssociatedView.swift @@ -186,7 +186,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) From 5c0501619aa457effccc5fb8784aa59b7c96aef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 17:49:45 +0900 Subject: [PATCH 10/14] feat: Reset home information when gallery host changed #156 --- EhPanda/DataFlow/AppAction.swift | 1 + EhPanda/DataFlow/Store.swift | 4 ++++ EhPanda/View/Home/HomeView.swift | 4 ++++ EhPanda/View/Setting/WebView.swift | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index ac6bf1e3..45b14291 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -12,6 +12,7 @@ import Foundation enum AppAction { case resetUser case resetFilters + case resetHomeInfo case setReadingProgress(gid: String, tag: Int) case setDiskImageCacheSize(size: String) case setAppIconType(_ iconType: IconType) diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index 1733ed38..9887de60 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -99,6 +99,10 @@ 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): diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index 31769267..db02a82f 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -13,6 +13,9 @@ 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 keyword = "" @State private var clipboardJumpID: String? @State private var isNavLinkActive = false @@ -59,6 +62,7 @@ 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) .customAlert( diff --git a/EhPanda/View/Setting/WebView.swift b/EhPanda/View/Setting/WebView.swift index 631f08dc..51570303 100644 --- a/EhPanda/View/Setting/WebView.swift +++ b/EhPanda/View/Setting/WebView.swift @@ -39,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) } } From 7dfcca598fbda352e96b1ac968211f0ee2e709fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 19:41:57 +0900 Subject: [PATCH 11/14] feat: Search suggestions optimization --- EhPanda.xcodeproj/project.pbxproj | 4 ++ EhPanda/DataFlow/AppAction.swift | 4 +- EhPanda/DataFlow/AppState.swift | 8 ++-- EhPanda/DataFlow/Store.swift | 8 ++-- EhPanda/View/Detail/AssociatedView.swift | 4 +- EhPanda/View/Home/HomeView.swift | 42 +++++++-------------- EhPanda/View/Tools/SuggestionProvider.swift | 38 +++++++++++++++++++ 7 files changed, 69 insertions(+), 39 deletions(-) create mode 100644 EhPanda/View/Tools/SuggestionProvider.swift diff --git a/EhPanda.xcodeproj/project.pbxproj b/EhPanda.xcodeproj/project.pbxproj index ad87805d..3ae552f7 100644 --- a/EhPanda.xcodeproj/project.pbxproj +++ b/EhPanda.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 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 */; }; @@ -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 = ""; }; @@ -586,6 +588,7 @@ ABF45AC725F3313D00ECB568 /* TagCloudView.swift */, ABBC332926BE7C940084A331 /* SettingTextField.swift */, AB0ABCB626C541A400AD970F /* WaveForm.swift */, + AB60D0EA274CFB6D00F899AB /* SuggestionProvider.swift */, ); path = Tools; sourceTree = ""; @@ -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 */, diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index 45b14291..8ee97a68 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -16,9 +16,9 @@ enum AppAction { case setReadingProgress(gid: String, tag: Int) case setDiskImageCacheSize(size: String) case setAppIconType(_ iconType: IconType) - case appendHistoryKeywords(text: String) + case appendHistoryKeyword(text: String) + case removeHistoryKeyword(text: String) case clearHistoryKeywords - case setLastKeyword(text: String) case setViewControllersCount case setSetting(_ setting: Setting) case setGalleryCommentJumpID(gid: String?) diff --git a/EhPanda/DataFlow/AppState.swift b/EhPanda/DataFlow/AppState.swift index a522649c..0039d25a 100644 --- a/EhPanda/DataFlow/AppState.swift +++ b/EhPanda/DataFlow/AppState.swift @@ -96,8 +96,6 @@ extension AppState { extension AppState { // MARK: HomeInfo struct HomeInfo { - var lastKeyword = "" - var searchItems = [Gallery]() var searchLoading = false var searchLoadError: AppError? @@ -172,8 +170,9 @@ extension AppState { } } } - mutating func insertHistoryKeyword(text: String) { + mutating func appendHistoryKeyword(text: String) { guard !text.isEmpty else { return } + var historyKeywords = historyKeywords if let index = historyKeywords.firstIndex(of: text) { if historyKeywords.last != text { historyKeywords.remove(at: index) @@ -190,6 +189,9 @@ extension AppState { } 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 9887de60..3131e3c6 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -109,12 +109,12 @@ final class Store: ObservableObject { appState.settings.setting.diskImageCacheSize = size case .setAppIconType(let iconType): appState.settings.setting.appIconType = iconType - case .appendHistoryKeywords(let text): - appState.homeInfo.insertHistoryKeyword(text: text) + case .appendHistoryKeyword(let text): + appState.homeInfo.appendHistoryKeyword(text: text) + 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: diff --git a/EhPanda/View/Detail/AssociatedView.swift b/EhPanda/View/Detail/AssociatedView.swift index 1256928d..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, diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index db02a82f..c7ab2809 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -17,6 +17,8 @@ struct HomeView: View, StoreAccessor { var galleryHost: GalleryHost @State private var keyword = "" + @State private var lastKeyword = "" + @State private var clipboardJumpID: String? @State private var isNavLinkActive = false @State private var greeting: Greeting? @@ -43,10 +45,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) @@ -54,6 +54,7 @@ struct HomeView: View, StoreAccessor { .navigationViewStyle(.stack) .onOpenURL(perform: tryOpenURL).onAppear(perform: onStartTasks) .sheet(item: environmentBinding.homeViewSheetState, content: sheet) + .onReceive(UIResponder.keyboardDidHideNotification.publisher, perform: tryUpdateHistoryKeywords) .onReceive(UIApplication.didBecomeActiveNotification.publisher, perform: onBecomeActive) .onChange(of: environment.galleryItemReverseLoading, perform: tryDismissLoadingHUD) .onChange(of: currentListTypePageNumber) { alertInput = String($0.current + 1) } @@ -64,7 +65,6 @@ struct HomeView: View, StoreAccessor { .onChange(of: environment.homeListType, perform: onHomeListTypeChange) .onChange(of: galleryHost) { _ in store.dispatch(.resetHomeInfo) } .onChange(of: user.greeting, perform: tryPresentNewDawnSheet) - .onChange(of: keyword, perform: tryUpdateHistoryKeywords) .customAlert( manager: alertManager, widthFactor: DeviceUtil.isPadWidth ? 0.5 : 1.0, backgroundOpacity: colorScheme == .light ? 0.2 : 0.5, @@ -80,22 +80,6 @@ struct HomeView: View, StoreAccessor { } 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 { @@ -457,7 +441,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?) { @@ -468,20 +452,20 @@ private extension HomeView { } // MARK: Search - func tryUpdateHistoryKeywords(_ keyword: String) { - guard keyword.isEmpty else { return } - store.dispatch(.appendHistoryKeywords(text: homeInfo.lastKeyword)) + func tryUpdateHistoryKeywords(_: Any) { + guard !lastKeyword.isEmpty else { return } + store.dispatch(.appendHistoryKeyword(text: lastKeyword)) } 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)) + lastKeyword = keyword } store.dispatch(.fetchSearchItems(keyword: keyword)) } @@ -549,7 +533,7 @@ private extension HomeView { } func fetchMoreSearchItems() { - store.dispatch(.fetchMoreSearchItems(keyword: homeInfo.lastKeyword)) + store.dispatch(.fetchMoreSearchItems(keyword: lastKeyword)) } func fetchMoreFrontpageItems() { store.dispatch(.fetchMoreFrontpageItems) 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 } + } + } +} From aa8a6c26dd5cda102c3591794b74453a3eba7659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 21:18:46 +0900 Subject: [PATCH 12/14] feat: Clear gallery history --- EhPanda/App/de.lproj/Localizable.strings | 9 ++- EhPanda/App/ja.lproj/Localizable.strings | 9 ++- EhPanda/App/ko.lproj/Localizable.strings | 9 ++- EhPanda/App/zh-Hans.lproj/Localizable.strings | 9 ++- EhPanda/App/zh-Hant.lproj/Localizable.strings | 9 ++- EhPanda/DataFlow/AppAction.swift | 3 - EhPanda/DataFlow/AppState.swift | 2 - EhPanda/DataFlow/Store.swift | 6 -- EhPanda/Database/Persistence.swift | 6 +- EhPanda/Database/PersistenceAccessor.swift | 10 +++- EhPanda/Models/Setting.swift | 1 - EhPanda/View/Home/FilterView.swift | 35 ++++-------- EhPanda/View/Home/HomeView.swift | 17 ++++++ EhPanda/View/Setting/AccountSettingView.swift | 21 ++++++- EhPanda/View/Setting/GeneralSettingView.swift | 43 ++++++++++++-- EhPanda/View/Setting/SettingView.swift | 57 +------------------ 16 files changed, 129 insertions(+), 117 deletions(-) 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 8ee97a68..93d76e47 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -14,7 +14,6 @@ enum AppAction { case resetFilters case resetHomeInfo case setReadingProgress(gid: String, tag: Int) - case setDiskImageCacheSize(size: String) case setAppIconType(_ iconType: IconType) case appendHistoryKeyword(text: String) case removeHistoryKeyword(text: String) @@ -39,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 0039d25a..e71423c3 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? diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index 3131e3c6..c920ddf2 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -105,8 +105,6 @@ final class Store: ObservableObject { 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 .appendHistoryKeyword(let text): @@ -161,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/Database/Persistence.swift b/EhPanda/Database/Persistence.swift index 9810e6fa..b7cf7260 100644 --- a/EhPanda/Database/Persistence.swift +++ b/EhPanda/Database/Persistence.swift @@ -128,8 +128,10 @@ struct PersistenceController { } } - static func batchUpdate(entityType: MO.Type, commitChanges: ([MO]) -> Void) { - let storedMOs = batchFetch(entityType: entityType) + static func batchUpdate( + entityType: MO.Type, predicate: NSPredicate? = nil, commitChanges: ([MO]) -> Void + ) { + let storedMOs = batchFetch(entityType: entityType, predicate: predicate, findBeforeFetch: false) commitChanges(storedMOs) saveContext() } diff --git a/EhPanda/Database/PersistenceAccessor.swift b/EhPanda/Database/PersistenceAccessor.swift index dab70c20..efcc652e 100644 --- a/EhPanda/Database/PersistenceAccessor.swift +++ b/EhPanda/Database/PersistenceAccessor.swift @@ -67,6 +67,14 @@ extension PersistenceController { 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, @@ -151,7 +159,7 @@ 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) } diff --git a/EhPanda/Models/Setting.swift b/EhPanda/Models/Setting.swift index 45748ec2..25d47f8b 100644 --- a/EhPanda/Models/Setting.swift +++ b/EhPanda/Models/Setting.swift @@ -16,7 +16,6 @@ struct Setting: Codable { // General @DefaultFalse var redirectsLinksToSelectedHost = false @DefaultFalse var detectsLinksFromPasteboard = false - @DefaultStringValue var diskImageCacheSize = "0 KB" @DefaultDoubleValue var backgroundBlurRadius = 10 @DefaultAutoLockPolicy var autoLockPolicy: AutoLockPolicy = .never 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 c7ab2809..fbcb3c68 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -29,6 +29,7 @@ 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 { @@ -76,6 +77,13 @@ 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) + } } } @@ -152,6 +160,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) 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/GeneralSettingView.swift b/EhPanda/View/Setting/GeneralSettingView.swift index 944f274f..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 } @@ -67,22 +70,27 @@ struct GeneralSettingView: View, StoreAccessor { } Section("Cache".localized) { Button { - store.dispatch(.setSettingViewActionSheetState(.clearImageCaches)) + 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 @@ -91,6 +99,10 @@ private extension GeneralSettingView { Locale.current.localizedString(forLanguageCode: Locale.current.languageCode ?? "") ?? "(null)" } + func onStartTasks() { + checkPasscodeExistence() + calculateDiskCachesSize() + } func checkPasscodeExistence() { var error: NSError? @@ -102,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 b233f33d..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,51 +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 .clearImageCaches: - 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() - PersistenceController.removeImageURLs() - calculateDiskCachesSize() - } } // MARK: SettingRow @@ -157,13 +109,6 @@ private struct SettingRow: View { } // MARK: Definition -enum SettingViewActionSheetState: Identifiable { - var id: Int { hashValue } - - case logout - case clearImageCaches -} - enum SettingViewSheetState: Identifiable { var id: Int { hashValue } From 731fa3e96caa9b00cc7a584196737a060a03013e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 21:31:15 +0900 Subject: [PATCH 13/14] fix: Search suggestions unexpected popups --- .github/workflows/deploy.yml | 2 +- EhPanda/DataFlow/AppAction.swift | 2 +- EhPanda/DataFlow/AppState.swift | 30 +++++++++++++++++------------- EhPanda/DataFlow/Store.swift | 4 ++-- EhPanda/View/Home/HomeView.swift | 29 +++++++++++++++++++++++++---- 5 files changed, 46 insertions(+), 21 deletions(-) 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/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index 93d76e47..6d6f815c 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -15,7 +15,7 @@ enum AppAction { case resetHomeInfo case setReadingProgress(gid: String, tag: Int) case setAppIconType(_ iconType: IconType) - case appendHistoryKeyword(text: String) + case appendHistoryKeywords(texts: [String]) case removeHistoryKeyword(text: String) case clearHistoryKeywords case setViewControllersCount diff --git a/EhPanda/DataFlow/AppState.swift b/EhPanda/DataFlow/AppState.swift index e71423c3..9efae269 100644 --- a/EhPanda/DataFlow/AppState.swift +++ b/EhPanda/DataFlow/AppState.swift @@ -168,21 +168,25 @@ extension AppState { } } } - mutating func appendHistoryKeyword(text: String) { - guard !text.isEmpty else { return } + mutating func appendHistoryKeywords(texts: [String]) { + guard !texts.isEmpty else { return } var historyKeywords = historyKeywords - if let index = historyKeywords.firstIndex(of: text) { - if historyKeywords.last != text { - historyKeywords.remove(at: index) + + 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 diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index c920ddf2..af9ef120 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -107,8 +107,8 @@ final class Store: ObservableObject { PersistenceController.update(gid: gid, readingProgress: tag) case .setAppIconType(let iconType): appState.settings.setting.appIconType = iconType - case .appendHistoryKeyword(let text): - appState.homeInfo.appendHistoryKeyword(text: text) + case .appendHistoryKeywords(let texts): + appState.homeInfo.appendHistoryKeywords(texts: texts) case .removeHistoryKeyword(let text): appState.homeInfo.removeHistoryKeyword(text: text) case .clearHistoryKeywords: diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index fbcb3c68..a63805f1 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -16,8 +16,10 @@ struct HomeView: View, StoreAccessor { @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 @@ -36,6 +38,7 @@ struct HomeView: View, StoreAccessor { NavigationView { ZStack { conditionalList + SearchHelper(isSearching: $isSearching) TTProgressHUD($hudVisible, config: hudConfig) } .background { @@ -55,7 +58,6 @@ struct HomeView: View, StoreAccessor { .navigationViewStyle(.stack) .onOpenURL(perform: tryOpenURL).onAppear(perform: onStartTasks) .sheet(item: environmentBinding.homeViewSheetState, content: sheet) - .onReceive(UIResponder.keyboardDidHideNotification.publisher, perform: tryUpdateHistoryKeywords) .onReceive(UIApplication.didBecomeActiveNotification.publisher, perform: onBecomeActive) .onChange(of: environment.galleryItemReverseLoading, perform: tryDismissLoadingHUD) .onChange(of: currentListTypePageNumber) { alertInput = String($0.current + 1) } @@ -66,6 +68,7 @@ struct HomeView: View, StoreAccessor { .onChange(of: environment.homeListType, perform: onHomeListTypeChange) .onChange(of: galleryHost) { _ in store.dispatch(.resetHomeInfo) } .onChange(of: user.greeting, perform: tryPresentNewDawnSheet) + .onChange(of: isSearching, perform: tryUpdateHistoryKeywords) .customAlert( manager: alertManager, widthFactor: DeviceUtil.isPadWidth ? 0.5 : 1.0, backgroundOpacity: colorScheme == .light ? 0.2 : 0.5, @@ -469,9 +472,10 @@ private extension HomeView { } // MARK: Search - func tryUpdateHistoryKeywords(_: Any) { - guard !lastKeyword.isEmpty else { return } - store.dispatch(.appendHistoryKeyword(text: lastKeyword)) + func tryUpdateHistoryKeywords(isSearching: Bool) { + guard !isSearching, !lastKeyword.isEmpty else { return } + store.dispatch(.appendHistoryKeywords(texts: pendingKeywords)) + pendingKeywords = [] } func tryRefetchSearchItems() { guard !lastKeyword.isEmpty else { return } @@ -482,6 +486,7 @@ private extension HomeView { store.dispatch(.setHomeListType(.search)) } if !keyword.isEmpty { + pendingKeywords.append(keyword) lastKeyword = keyword } store.dispatch(.fetchSearchItems(keyword: keyword)) @@ -579,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 } From 8bc299812d069f94d5fd8d328f289edc6282f647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=A4=E3=81=9D=E3=82=99=E3=81=86?= Date: Tue, 23 Nov 2021 22:57:43 +0900 Subject: [PATCH 14/14] feat: Better webtoon cover handling approach --- EhPanda/App/Defaults.swift | 16 ++--- EhPanda/App/Extensions.swift | 4 +- EhPanda/App/Tools/Parser.swift | 6 +- EhPanda/App/ViewModifiers.swift | 63 ++++++++++--------- EhPanda/View/Detail/DetailView.swift | 8 +-- EhPanda/View/Reading/ControlPanel.swift | 4 +- EhPanda/View/Reading/ReadingView.swift | 2 +- EhPanda/View/Tools/GalleryDetailCell.swift | 2 +- EhPanda/View/Tools/GalleryThumbnailCell.swift | 8 ++- 9 files changed, 60 insertions(+), 53 deletions(-) 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 e4891414..06d94ac9 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -1508,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) @@ -1517,11 +1517,11 @@ extension Parser { let plainURL = String(string[.. 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/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/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/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 e2b7e32c..b81091eb 100644 --- a/EhPanda/View/Tools/GalleryThumbnailCell.swift +++ b/EhPanda/View/Tools/GalleryThumbnailCell.swift @@ -24,9 +24,13 @@ 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)) } + .placeholder { Placeholder(style: .activity(ratio: Defaults.ImageSize.rowAspect)) } + .imageModifier(WebtoonModifier( + minAspect: Defaults.ImageSize.webtoonMinAspect, + idealAspect: Defaults.ImageSize.webtoonIdealAspect + )) /*.fade(duration: 0.25)*/ - .resizable().frame(maxHeight: DeviceUtil.absWindowH * 2/3).scaledToFit().overlay { + .resizable().scaledToFit().overlay { VStack { HStack { Spacer()