From 1ffe04e1a412ed8443dc5e278349945896613ad0 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: Sat, 27 Nov 2021 09:33:39 +0900 Subject: [PATCH 01/12] fix: Restore images fade effect --- EhPanda.xcodeproj/project.pbxproj | 34 +++++++++---------- .../xcshareddata/swiftpm/Package.resolved | 6 ++-- EhPanda/App/ViewModifiers.swift | 2 +- EhPanda/View/Detail/DetailView.swift | 4 +-- EhPanda/View/Home/SlideMenu.swift | 2 +- EhPanda/View/Reading/ControlPanel.swift | 2 +- EhPanda/View/Reading/ReadingView.swift | 2 +- EhPanda/View/Tools/GalleryThumbnailCell.swift | 3 +- 8 files changed, 27 insertions(+), 28 deletions(-) diff --git a/EhPanda.xcodeproj/project.pbxproj b/EhPanda.xcodeproj/project.pbxproj index f3838757..c4424c03 100644 --- a/EhPanda.xcodeproj/project.pbxproj +++ b/EhPanda.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 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 */; }; @@ -73,6 +72,7 @@ ABC3C7892593699B00E0C11B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC3C76D2593699A00E0C11B /* Defaults.swift */; }; ABC3C78F2593699B00E0C11B /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC3C7762593699A00E0C11B /* ViewModifiers.swift */; }; ABC3C7962593699B00E0C11B /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC3C7802593699A00E0C11B /* Models.swift */; }; + ABC4A0792751B40E00968A4F /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = ABC4A0782751B40E00968A4F /* Kingfisher */; }; ABC681F326898D46007BBD69 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = ABC681F126898D46007BBD69 /* Model.xcdatamodeld */; }; ABCA93BE26918DE100A98BC6 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCA93BD26918DE100A98BC6 /* Persistence.swift */; }; ABCA93C02691925900A98BC6 /* GalleryMO+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCA93BF2691925900A98BC6 /* GalleryMO+CoreDataClass.swift */; }; @@ -301,8 +301,8 @@ AB19D619266E5C6700BA752A /* TTProgressHUD in Frameworks */, ABE9402D26FF89220085E158 /* AlertKit in Frameworks */, AB60D0E9274C7ECE00F899AB /* WaterfallGrid in Frameworks */, + ABC4A0792751B40E00968A4F /* Kingfisher in Frameworks */, AB0F68AF26A6D92F00AC3A54 /* DeprecatedAPI in Frameworks */, - AB21CC9A274B4ED700C115B1 /* Kingfisher in Frameworks */, AB60D0CF274C7AA000F899AB /* BetterCodable in Frameworks */, AB6505A026B0027800F91E9D /* SwiftUIPager in Frameworks */, AB21CCA0274B4F0C00C115B1 /* SwiftyBeaver in Frameworks */, @@ -681,10 +681,10 @@ ABD7005826B1C31500DC59C9 /* Kanna */, ABAC82FD26BC4A96009F5026 /* OpenCC */, ABE9402C26FF89220085E158 /* AlertKit */, - AB21CC99274B4ED700C115B1 /* Kingfisher */, AB21CC9F274B4F0C00C115B1 /* SwiftyBeaver */, AB60D0CE274C7AA000F899AB /* BetterCodable */, AB60D0E8274C7ECE00F899AB /* WaterfallGrid */, + ABC4A0782751B40E00968A4F /* Kingfisher */, ); productName = EhPanda; productReference = ABC3C7542593696C00E0C11B /* EhPanda.app */; @@ -750,10 +750,10 @@ ABD7005726B1C31500DC59C9 /* XCRemoteSwiftPackageReference "Kanna" */, ABAC82FC26BC4866009F5026 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */, ABE9402B26FF89220085E158 /* XCRemoteSwiftPackageReference "AlertKit" */, - AB21CC98274B4ED700C115B1 /* XCRemoteSwiftPackageReference "Kingfisher" */, AB21CC9E274B4F0C00C115B1 /* XCRemoteSwiftPackageReference "SwiftyBeaver" */, AB60D0CD274C7AA000F899AB /* XCRemoteSwiftPackageReference "BetterCodable" */, AB60D0E7274C7ECE00F899AB /* XCRemoteSwiftPackageReference "WaterfallGrid" */, + ABC4A0772751B40E00968A4F /* XCRemoteSwiftPackageReference "Kingfisher" */, ); productRefGroup = ABC3C7552593696C00E0C11B /* Products */; projectDirPath = ""; @@ -1326,14 +1326,6 @@ kind = branch; }; }; - AB21CC98274B4ED700C115B1 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/tatsuz0u/Kingfisher.git"; - requirement = { - branch = "fix/binder-loaded"; - kind = branch; - }; - }; AB21CC9E274B4F0C00C115B1 /* XCRemoteSwiftPackageReference "SwiftyBeaver" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/tatsuz0u/SwiftyBeaver.git"; @@ -1374,6 +1366,14 @@ minimumVersion = "2.0.0-beta"; }; }; + ABC4A0772751B40E00968A4F /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher.git"; + requirement = { + branch = master; + kind = branch; + }; + }; ABD7005726B1C31500DC59C9 /* XCRemoteSwiftPackageReference "Kanna" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/tid-kijyun/Kanna.git"; @@ -1403,11 +1403,6 @@ package = AB19D617266E5C6700BA752A /* XCRemoteSwiftPackageReference "TTProgressHUD" */; productName = TTProgressHUD; }; - AB21CC99274B4ED700C115B1 /* Kingfisher */ = { - isa = XCSwiftPackageProductDependency; - package = AB21CC98274B4ED700C115B1 /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; - }; AB21CC9F274B4F0C00C115B1 /* SwiftyBeaver */ = { isa = XCSwiftPackageProductDependency; package = AB21CC9E274B4F0C00C115B1 /* XCRemoteSwiftPackageReference "SwiftyBeaver" */; @@ -1433,6 +1428,11 @@ package = ABAC82FC26BC4866009F5026 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */; productName = OpenCC; }; + ABC4A0782751B40E00968A4F /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = ABC4A0772751B40E00968A4F /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; ABD7005826B1C31500DC59C9 /* Kanna */ = { isa = XCSwiftPackageProductDependency; package = ABD7005726B1C31500DC59C9 /* XCRemoteSwiftPackageReference "Kanna" */; diff --git a/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0eeacaed..bebbb4d0 100644 --- a/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -39,10 +39,10 @@ }, { "package": "Kingfisher", - "repositoryURL": "https://github.com/tatsuz0u/Kingfisher.git", + "repositoryURL": "https://github.com/onevcat/Kingfisher.git", "state": { - "branch": "fix/binder-loaded", - "revision": "57744dfc08e6594f3df4490b12a89746ebf0f094", + "branch": "master", + "revision": "c874e5ef178a65b6a2b6a04d5981652d4fa95e22", "version": null } }, diff --git a/EhPanda/App/ViewModifiers.swift b/EhPanda/App/ViewModifiers.swift index 7d664e1f..940f624e 100644 --- a/EhPanda/App/ViewModifiers.swift +++ b/EhPanda/App/ViewModifiers.swift @@ -119,7 +119,7 @@ extension KFImage { .imageModifier(CornersModifier( radius: withRoundedCorners ? 5 : nil )) -// .fade(duration: 0.25) + .fade(duration: 0.25) .resizable() } } diff --git a/EhPanda/View/Detail/DetailView.swift b/EhPanda/View/Detail/DetailView.swift index deb887be..2d2c8f53 100644 --- a/EhPanda/View/Detail/DetailView.swift +++ b/EhPanda/View/Detail/DetailView.swift @@ -683,7 +683,7 @@ private struct PreviewView: View { )) } .imageModifier(modifier) -// .fade(duration: 0.25) + .fade(duration: 0.25) .resizable().scaledToFit() .frame(width: width, height: height) .onTapGesture { tapAction(index, true) } @@ -749,7 +749,7 @@ private struct MorePreviewView: View { )) } .imageModifier(modifier) -// .fade(duration: 0.25) + .fade(duration: 0.25) .resizable().scaledToFit() .onTapGesture { tapAction(index, false) diff --git a/EhPanda/View/Home/SlideMenu.swift b/EhPanda/View/Home/SlideMenu.swift index 36552463..b155c0fd 100644 --- a/EhPanda/View/Home/SlideMenu.swift +++ b/EhPanda/View/Home/SlideMenu.swift @@ -105,7 +105,7 @@ private struct AvatarView: View { } else { KFAnimatedImage(URL(string: avatarURL ?? "")) .placeholder(placeholder) -// .fade(duration: 0.25) + .fade(duration: 0.25) .retry(maxCount: 10) } } diff --git a/EhPanda/View/Reading/ControlPanel.swift b/EhPanda/View/Reading/ControlPanel.swift index cf91e8f1..e9eb6b20 100644 --- a/EhPanda/View/Reading/ControlPanel.swift +++ b/EhPanda/View/Reading/ControlPanel.swift @@ -272,7 +272,7 @@ private struct SliderPreivew: View { ratio: Defaults.ImageSize.previewAspect )) } -// .fade(duration: 0.25) + .fade(duration: 0.25) .imageModifier(modifier).resizable().scaledToFit() .frame(width: previewWidth, height: isSliderDragging ? previewHeight : 0) Text("\(index)").font(DeviceUtil.isPadWidth ? .callout : .caption) diff --git a/EhPanda/View/Reading/ReadingView.swift b/EhPanda/View/Reading/ReadingView.swift index d77e7372..978c4f17 100644 --- a/EhPanda/View/Reading/ReadingView.swift +++ b/EhPanda/View/Reading/ReadingView.swift @@ -673,7 +673,7 @@ private struct ImageContainer: View { .onSuccess(onSuccess).onFailure(onFailure) } else { KFAnimatedImage(URL(string: imageURL)) - .placeholder(placeholder)// .fade(duration: 0.25) + .placeholder(placeholder).fade(duration: 0.25) .onSuccess(onSuccess).onFailure(onFailure) } } diff --git a/EhPanda/View/Tools/GalleryThumbnailCell.swift b/EhPanda/View/Tools/GalleryThumbnailCell.swift index b81091eb..39c724a5 100644 --- a/EhPanda/View/Tools/GalleryThumbnailCell.swift +++ b/EhPanda/View/Tools/GalleryThumbnailCell.swift @@ -29,8 +29,7 @@ struct GalleryThumbnailCell: View { minAspect: Defaults.ImageSize.webtoonMinAspect, idealAspect: Defaults.ImageSize.webtoonIdealAspect )) - /*.fade(duration: 0.25)*/ - .resizable().scaledToFit().overlay { + .fade(duration: 0.25).resizable().scaledToFit().overlay { VStack { HStack { Spacer() From cd268d41effea098c500706eff459dbf57a989d4 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: Sat, 27 Nov 2021 10:28:25 +0900 Subject: [PATCH 02/12] feat: Tap to next / previous page #157 --- EhPanda/View/Reading/ReadingView.swift | 39 +++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/EhPanda/View/Reading/ReadingView.swift b/EhPanda/View/Reading/ReadingView.swift index 978c4f17..2e954782 100644 --- a/EhPanda/View/Reading/ReadingView.swift +++ b/EhPanda/View/Reading/ReadingView.swift @@ -321,16 +321,11 @@ private extension ReadingView { ) } func tryUpdatePagerIndexByTimer(_: Timer) { - let distance = DeviceUtil.isLandscape && setting.enablesDualPageMode - && setting.readingDirection != .vertical ? 2 : 1 - - guard Int(sliderValue) + distance <= pageCount else { + guard Int(sliderValue) < pageCount else { autoPlayPolicy = .never return } - - sliderValue += Float(distance) - tryUpdatePagerIndex() + page.update(.next) } func trySaveReadingProgress() { let progress = mapFromPager(index: page.index) @@ -491,16 +486,30 @@ private extension ReadingView { // MARK: Gesture var tapGesture: some Gesture { let singleTap = TapGesture(count: 1) - .onEnded { _ in withAnimation { showsPanel.toggle() } } + .onEnded { _ in + let defaultAction = { withAnimation { showsPanel.toggle() } } + guard setting.readingDirection != .vertical, + let pointX = TouchHandler.shared.currentPoint?.x + else { + defaultAction() + return + } + let rightToLeft = setting.readingDirection == .rightToLeft + if pointX < DeviceUtil.absWindowW * 0.2 { + page.update(rightToLeft ? .next : .previous) + } else if pointX > DeviceUtil.absWindowW * (1 - 0.2) { + page.update(rightToLeft ? .previous : .next) + } else { + defaultAction() + } + } let doubleTap = TapGesture(count: 2) .onEnded { _ in trySyncScaleAnchor() trySetOffset(.zero) trySetScale(scale == 1 ? setting.doubleTapScaleFactor : 1) } - return ExclusiveGesture( - doubleTap, singleTap - ) + return ExclusiveGesture(doubleTap, singleTap) } var magnifyGesture: some Gesture { MagnificationGesture() @@ -508,12 +517,8 @@ private extension ReadingView { .onEnded(onMagnificationGestureEnded) } var dragGesture: some Gesture { - DragGesture( - minimumDistance: 0.0, - coordinateSpace: .local - ) - .onChanged(onDragGestureChanged) - .onEnded(onDragGestureEnded) + DragGesture(minimumDistance: 0.0, coordinateSpace: .local) + .onChanged(onDragGestureChanged).onEnded(onDragGestureEnded) } func onDragGestureChanged(value: DragGesture.Value) { From b3728bde85c42047b00b827e184c2fb3722db08d 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: Sat, 27 Nov 2021 11:25:46 +0900 Subject: [PATCH 03/12] feat: Quick search query alias #157 --- EhPanda/App/de.lproj/Localizable.strings | 1 + EhPanda/App/ja.lproj/Localizable.strings | 1 + EhPanda/App/ko.lproj/Localizable.strings | 1 + EhPanda/App/zh-Hans.lproj/Localizable.strings | 1 + EhPanda/App/zh-Hant.lproj/Localizable.strings | 1 + EhPanda/DataFlow/Store.swift | 4 +- EhPanda/Models/Misc.swift | 3 +- EhPanda/View/Home/QuickSearchView.swift | 75 ++++++++++++------- EhPanda/View/Reading/ControlPanel.swift | 14 ++-- EhPanda/View/Reading/ReadingView.swift | 42 +++++------ 10 files changed, 85 insertions(+), 58 deletions(-) diff --git a/EhPanda/App/de.lproj/Localizable.strings b/EhPanda/App/de.lproj/Localizable.strings index 42b9ecc7..148b7f54 100644 --- a/EhPanda/App/de.lproj/Localizable.strings +++ b/EhPanda/App/de.lproj/Localizable.strings @@ -359,6 +359,7 @@ // MARK: QuickSearchView //"Quick search" = ""; +//"Alias" = ""; // MARK: HomeListType "Search" = "Suche"; diff --git a/EhPanda/App/ja.lproj/Localizable.strings b/EhPanda/App/ja.lproj/Localizable.strings index 4361fe2e..b82f85c6 100644 --- a/EhPanda/App/ja.lproj/Localizable.strings +++ b/EhPanda/App/ja.lproj/Localizable.strings @@ -359,6 +359,7 @@ // MARK: QuickSearchView "Quick search" = "クイック検索"; +"Alias" = "エイリアス"; // MARK: HomeListType "Search" = "検索"; diff --git a/EhPanda/App/ko.lproj/Localizable.strings b/EhPanda/App/ko.lproj/Localizable.strings index ce5b1e0f..b534ce65 100644 --- a/EhPanda/App/ko.lproj/Localizable.strings +++ b/EhPanda/App/ko.lproj/Localizable.strings @@ -359,6 +359,7 @@ // MARK: QuickSearchView "Quick search" = "빠른 검색"; +//"Alias" = ""; // MARK: HomeListType "Search" = "검색"; diff --git a/EhPanda/App/zh-Hans.lproj/Localizable.strings b/EhPanda/App/zh-Hans.lproj/Localizable.strings index d22ac730..facc81c8 100644 --- a/EhPanda/App/zh-Hans.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hans.lproj/Localizable.strings @@ -359,6 +359,7 @@ // MARK: QuickSearchView "Quick search" = "快速搜索"; +"Alias" = "别称"; // MARK: HomeListType "Search" = "搜索"; diff --git a/EhPanda/App/zh-Hant.lproj/Localizable.strings b/EhPanda/App/zh-Hant.lproj/Localizable.strings index b4f5db51..2a586954 100644 --- a/EhPanda/App/zh-Hant.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hant.lproj/Localizable.strings @@ -359,6 +359,7 @@ // MARK: QuickSearchView "Quick search" = "快速搜尋"; +//"Alias" = ""; // MARK: HomeListType "Search" = "搜尋"; diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index af9ef120..8f7cf36e 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -19,9 +19,7 @@ final class Store: ObservableObject { func dispatch(_ action: AppAction) { #if DEBUG - guard !appState.environment.isPreview, - !AppUtil.isUnitTesting - else { return } + guard !AppUtil.isUnitTesting else { return } #endif if Thread.isMainThread { diff --git a/EhPanda/Models/Misc.swift b/EhPanda/Models/Misc.swift index d0bf7b95..44ff3b07 100644 --- a/EhPanda/Models/Misc.swift +++ b/EhPanda/Models/Misc.swift @@ -90,7 +90,8 @@ struct Greeting: Codable, Equatable { } } -struct QuickSearchWord: Codable, Identifiable { +struct QuickSearchWord: Codable, Equatable, Identifiable { var id = UUID().uuidString + var alias: String? let content: String } diff --git a/EhPanda/View/Home/QuickSearchView.swift b/EhPanda/View/Home/QuickSearchView.swift index d5cba8f8..e7f3501c 100644 --- a/EhPanda/View/Home/QuickSearchView.swift +++ b/EhPanda/View/Home/QuickSearchView.swift @@ -10,7 +10,7 @@ import SwiftUI struct QuickSearchView: View, StoreAccessor { @EnvironmentObject var store: Store @State private var isEditting = false - @State private var refreshID = UUID().uuidString + @State private var refreshTrigger = UUID().uuidString private let searchAction: (String) -> Void @@ -26,14 +26,14 @@ struct QuickSearchView: View, StoreAccessor { ForEach(words) { word in QuickSearchWordRow( word: word, isEditting: $isEditting, - submitID: $refreshID, searchAction: searchAction, + refreshTrigger: $refreshTrigger, searchAction: searchAction, submitAction: { store.dispatch(.modifyQuickSearchWord(newWord: $0)) } ) } .onDelete { store.dispatch(.deleteQuickSearchWord(offsets: $0)) } .onMove(perform: move) } - .id(refreshID) + .id(refreshTrigger) ErrorView(error: .notFound, retryAction: nil).opacity(words.isEmpty ? 1 : 0) } .environment(\.editMode, .constant(isEditting ? .active : .inactive)) @@ -67,68 +67,93 @@ private extension QuickSearchView { homeInfo.quickSearchWords } func move(from source: IndexSet, to destination: Int) { - refreshID = UUID().uuidString + refreshTrigger = UUID().uuidString store.dispatch(.moveQuickSearchWord(source: source, destination: destination)) } } // MARK: QuickSearchWordRow private struct QuickSearchWordRow: View { - @FocusState private var isFocused + @FocusState private var focusField: FocusField? + @State private var editableAlias: String @State private var editableContent: String private var plainWord: QuickSearchWord @Binding private var isEditting: Bool - @Binding private var submitID: String + @Binding private var refreshTrigger: String private var searchAction: (String) -> Void private var submitAction: (QuickSearchWord) -> Void + enum FocusField { + case alias + case content + } + init( word: QuickSearchWord, isEditting: Binding, - submitID: Binding, + refreshTrigger: Binding, searchAction: @escaping (String) -> Void, submitAction: @escaping (QuickSearchWord) -> Void ) { + _editableAlias = State(initialValue: word.alias ?? "") _editableContent = State(initialValue: word.content) plainWord = word _isEditting = isEditting - _submitID = submitID + _refreshTrigger = refreshTrigger self.searchAction = searchAction self.submitAction = submitAction } + private var title: String { + if let alias = plainWord.alias, !alias.isEmpty { + return alias + } else { + return plainWord.content + } + } + var body: some View { ZStack { - Button(plainWord.content) { - searchAction(plainWord.content) + if isEditting { + VStack { + TextField(editableAlias, text: $editableAlias, prompt: Text("Alias")) + .submitLabel(.next).lineLimit(1).focused($focusField, equals: .alias) + Divider().foregroundColor(.secondary.opacity(0.2)) + TextEditor(text: $editableContent).textInputAutocapitalization(.none) + .disableAutocorrection(true).focused($focusField, equals: .content) + } + } else { + Button(title) { + searchAction(plainWord.content) + } + .withArrow().foregroundColor(.primary) } - .withArrow().foregroundColor(.primary) - .opacity(isEditting ? 0 : 1) - TextEditor(text: $editableContent) - .textInputAutocapitalization(.none) - .disableAutocorrection(true) - .opacity(isEditting ? 1 : 0) - .focused($isFocused) } - .onChange(of: isFocused, perform: trySubmit) - .onChange(of: submitID, perform: trySubmit) - .onChange(of: isEditting) { _ in - trySubmit() - isFocused = false + .onChange(of: isEditting) { _ in focusField = nil } + .onChange(of: refreshTrigger, perform: trySubmit) + .onChange(of: focusField, perform: trySubmit) + .onSubmit { + switch focusField { + case .alias: + focusField = .content + default: + focusField = nil + } } } private func trySubmit(_: Any? = nil) { - guard editableContent != plainWord.content else { return } - submitAction(QuickSearchWord(id: plainWord.id, content: editableContent)) + var newWord = QuickSearchWord(id: plainWord.id, content: editableContent) + if !editableAlias.isEmpty { newWord.alias = editableAlias } + guard newWord != plainWord else { return } + submitAction(newWord) } } struct QuickSearchView_Previews: PreviewProvider { static var previews: some View { QuickSearchView(searchAction: { _ in }) - .preferredColorScheme(.dark) .environmentObject(Store.preview) } } diff --git a/EhPanda/View/Reading/ControlPanel.swift b/EhPanda/View/Reading/ControlPanel.swift index e9eb6b20..983ff5d7 100644 --- a/EhPanda/View/Reading/ControlPanel.swift +++ b/EhPanda/View/Reading/ControlPanel.swift @@ -10,7 +10,7 @@ import Kingfisher // MARK: ControlPanel struct ControlPanel: View { - @State private var refreshID = UUID().uuidString + @State private var refreshTrigger = UUID().uuidString @Binding private var showsPanel: Bool @Binding private var sliderValue: Float @@ -49,7 +49,7 @@ struct ControlPanel: View { VStack { UpperPanel( title: "\(currentIndex) / " + "\(Int(range.upperBound))", - setting: $setting, refreshID: $refreshID, + setting: $setting, refreshTrigger: $refreshTrigger, autoPlayPolicy: $autoPlayPolicy, settingAction: settingAction, updateSettingAction: updateSettingAction @@ -69,7 +69,7 @@ struct ControlPanel: View { .onChange(of: showsPanel) { newValue in guard newValue else { return } // workaround DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - refreshID = UUID().uuidString + refreshTrigger = UUID().uuidString } } } @@ -79,7 +79,7 @@ struct ControlPanel: View { private struct UpperPanel: View { @Environment(\.dismiss) var dismissAction @Binding private var setting: Setting - @Binding private var refreshID: String + @Binding private var refreshTrigger: String @Binding private var autoPlayPolicy: AutoPlayPolicy private let title: String @@ -88,12 +88,12 @@ private struct UpperPanel: View { init( title: String, setting: Binding, - refreshID: Binding, autoPlayPolicy: Binding, + refreshTrigger: Binding, autoPlayPolicy: Binding, settingAction: @escaping () -> Void, updateSettingAction: @escaping (Setting) -> Void ) { self.title = title _setting = setting - _refreshID = refreshID + _refreshTrigger = refreshTrigger _autoPlayPolicy = autoPlayPolicy self.settingAction = settingAction self.updateSettingAction = updateSettingAction @@ -156,7 +156,7 @@ private struct UpperPanel: View { Image(systemName: "timer") } } - .id(refreshID) + .id(refreshTrigger) Button(action: settingAction) { Image(systemName: "gear") } diff --git a/EhPanda/View/Reading/ReadingView.swift b/EhPanda/View/Reading/ReadingView.swift index 2e954782..1d762d37 100644 --- a/EhPanda/View/Reading/ReadingView.swift +++ b/EhPanda/View/Reading/ReadingView.swift @@ -485,30 +485,28 @@ private extension ReadingView { // MARK: Gesture var tapGesture: some Gesture { - let singleTap = TapGesture(count: 1) - .onEnded { _ in - let defaultAction = { withAnimation { showsPanel.toggle() } } - guard setting.readingDirection != .vertical, - let pointX = TouchHandler.shared.currentPoint?.x - else { - defaultAction() - return - } - let rightToLeft = setting.readingDirection == .rightToLeft - if pointX < DeviceUtil.absWindowW * 0.2 { - page.update(rightToLeft ? .next : .previous) - } else if pointX > DeviceUtil.absWindowW * (1 - 0.2) { - page.update(rightToLeft ? .previous : .next) - } else { - defaultAction() - } + let singleTap = TapGesture(count: 1).onEnded { _ in + let defaultAction = { withAnimation { showsPanel.toggle() } } + guard setting.readingDirection != .vertical, + let pointX = TouchHandler.shared.currentPoint?.x + else { + defaultAction() + return } - let doubleTap = TapGesture(count: 2) - .onEnded { _ in - trySyncScaleAnchor() - trySetOffset(.zero) - trySetScale(scale == 1 ? setting.doubleTapScaleFactor : 1) + let rightToLeft = setting.readingDirection == .rightToLeft + if pointX < DeviceUtil.absWindowW * 0.2 { + page.update(rightToLeft ? .next : .previous) + } else if pointX > DeviceUtil.absWindowW * (1 - 0.2) { + page.update(rightToLeft ? .previous : .next) + } else { + defaultAction() } + } + let doubleTap = TapGesture(count: 2).onEnded { _ in + trySyncScaleAnchor() + trySetOffset(.zero) + trySetScale(scale == 1 ? setting.doubleTapScaleFactor : 1) + } return ExclusiveGesture(doubleTap, singleTap) } var magnifyGesture: some Gesture { From b711858a1d14f9d0bddebd5c12f769bf87750a51 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: Sat, 27 Nov 2021 16:41:10 +0900 Subject: [PATCH 04/12] feat: Give the swipe-back gesture a higher priority #180 --- EhPanda/App/Extensions.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/EhPanda/App/Extensions.swift b/EhPanda/App/Extensions.swift index d857954d..3fb50d6c 100644 --- a/EhPanda/App/Extensions.swift +++ b/EhPanda/App/Extensions.swift @@ -9,16 +9,17 @@ import SwiftUI import SwiftyBeaver // MARK: UINavigationController -// Enables fullscreen swipe back gesture extension UINavigationController: UIGestureRecognizerDelegate { + // Enables the swipe-back gesture in fullscreen override open func viewDidLoad() { super.viewDidLoad() interactivePopGestureRecognizer?.delegate = self } - - public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - viewControllers.count > 1 - } + // Give the swipe-back gesture a higher priority + public func gestureRecognizer( + _ gestureRecognizer: UIGestureRecognizer, + shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer + ) -> Bool { gestureRecognizer.isKind(of: UIScreenEdgePanGestureRecognizer.self) } } // MARK: Encodable From 53aa3b15e95289f7bfcb2eab91b46cde42bbdda5 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: Sat, 27 Nov 2021 18:04:00 +0900 Subject: [PATCH 05/12] refactor: Better after-login experience --- EhPanda/App/Extensions.swift | 2 +- EhPanda/View/Setting/LoginView.swift | 4 +++- EhPanda/View/Setting/WebView.swift | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/EhPanda/App/Extensions.swift b/EhPanda/App/Extensions.swift index 3fb50d6c..2345e33a 100644 --- a/EhPanda/App/Extensions.swift +++ b/EhPanda/App/Extensions.swift @@ -15,7 +15,7 @@ extension UINavigationController: UIGestureRecognizerDelegate { super.viewDidLoad() interactivePopGestureRecognizer?.delegate = self } - // Give the swipe-back gesture a higher priority + // Gives the swipe-back gesture a higher priority public func gestureRecognizer( _ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer diff --git a/EhPanda/View/Setting/LoginView.swift b/EhPanda/View/Setting/LoginView.swift index 4d370aaf..07aaf3a7 100644 --- a/EhPanda/View/Setting/LoginView.swift +++ b/EhPanda/View/Setting/LoginView.swift @@ -105,9 +105,11 @@ struct LoginView: View, StoreAccessor { } HapticUtil.generateNotificationFeedback(style: .success) dismissAction.callAsFunction() - store.dispatch(.fetchFrontpageItems()) store.dispatch(.verifyEhProfile) store.dispatch(.fetchUserInfo) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + store.dispatch(.resetHomeInfo) + } } token.unseal() } receiveValue: { value in diff --git a/EhPanda/View/Setting/WebView.swift b/EhPanda/View/Setting/WebView.swift index 51570303..07432fbc 100644 --- a/EhPanda/View/Setting/WebView.swift +++ b/EhPanda/View/Setting/WebView.swift @@ -40,8 +40,10 @@ struct WebView: UIViewControllerRepresentable { let store = self?.parent.store store?.dispatch(.setSettingViewSheetState(nil)) store?.dispatch(.verifyEhProfile) - store?.dispatch(.resetHomeInfo) store?.dispatch(.fetchUserInfo) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + store?.dispatch(.resetHomeInfo) + } } } From 6c6c3bfcfc0f00bb1b186380e72c351bb5a12eab 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: Sat, 27 Nov 2021 18:18:37 +0900 Subject: [PATCH 06/12] refactor: Storing `thumbnail` as `String` --- EhPanda/App/Tools/Parser.swift | 11 +++++------ EhPanda/DataFlow/AppAction.swift | 4 ++-- EhPanda/DataFlow/AppCommand.swift | 4 ++-- EhPanda/DataFlow/AppState.swift | 4 ++-- EhPanda/DataFlow/Store.swift | 2 +- .../MODefinition/GalleryStateMO+CoreDataClass.swift | 2 +- EhPanda/Database/PersistenceAccessor.swift | 4 ++-- EhPanda/Models/Models.swift | 2 +- EhPanda/Network/Request.swift | 13 +++++++------ 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/EhPanda/App/Tools/Parser.swift b/EhPanda/App/Tools/Parser.swift index 06d94ac9..124d2458 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -547,8 +547,8 @@ struct Parser { } // MARK: Content - static func parseThumbnails(doc: HTMLDocument) throws -> [Int: URL] { - var thumbnails = [Int: URL]() + static func parseThumbnails(doc: HTMLDocument) throws -> [Int: String] { + var thumbnails = [Int: String]() guard let gdtNode = doc.at_xpath("//div [@id='gdt']"), let previewMode = try? parsePreviewMode(doc: doc) @@ -556,12 +556,11 @@ struct Parser { for link in gdtNode.xpath("//div [@class='\(previewMode)']") { guard let aLink = link.at_xpath("//a"), - let thumbnailString = aLink["href"], - let thumbnailURL = URL(string: thumbnailString), - let index = Int(aLink.at_xpath("//img")?["alt"] ?? "") + let thumbnail = aLink["href"], + let index = Int(aLink.at_xpath("//img")?["alt"] ?? "") else { continue } - thumbnails[index] = thumbnailURL + thumbnails[index] = thumbnail } return thumbnails diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index 6d6f815c..3977aece 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -84,8 +84,8 @@ enum AppAction { case fetchMPVKeys(gid: String, index: Int, mpvURL: String) case fetchMPVKeysDone(gid: String, index: Int, result: Result<(String, [Int: String]), AppError>) case fetchThumbnails(gid: String, index: Int) - case fetchThumbnailsDone(gid: String, index: Int, result: Result<[Int: URL], AppError>) - case fetchGalleryNormalContents(gid: String, index: Int, thumbnails: [Int: URL]) + case fetchThumbnailsDone(gid: String, index: Int, result: Result<[Int: String], AppError>) + case fetchGalleryNormalContents(gid: String, index: Int, thumbnails: [Int: String]) case fetchGalleryNormalContentsDone(gid: String, index: Int, result: Result<[Int: String], AppError>) case refetchGalleryNormalContent(gid: String, index: Int) case refetchGalleryNormalContentDone(gid: String, index: Int, result: Result<[Int: String], AppError>) diff --git a/EhPanda/DataFlow/AppCommand.swift b/EhPanda/DataFlow/AppCommand.swift index 92d4c2e5..aabf43bb 100644 --- a/EhPanda/DataFlow/AppCommand.swift +++ b/EhPanda/DataFlow/AppCommand.swift @@ -541,7 +541,7 @@ struct FetchThumbnailsCommand: AppCommand { struct FetchGalleryNormalContentsCommand: AppCommand { let gid: String let index: Int - let thumbnails: [Int: URL] + let thumbnails: [Int: String] func execute(in store: Store) { let token = SubscriptionToken() @@ -569,7 +569,7 @@ struct RefetchGalleryNormalContentCommand: AppCommand { let gid: String let index: Int let galleryURL: String - let thumbnailURL: URL? + let thumbnailURL: String? let storedImageURL: String let bypassesSNIFiltering: Bool diff --git a/EhPanda/DataFlow/AppState.swift b/EhPanda/DataFlow/AppState.swift index 9efae269..4650a542 100644 --- a/EhPanda/DataFlow/AppState.swift +++ b/EhPanda/DataFlow/AppState.swift @@ -243,7 +243,7 @@ extension AppState { // MARK: ContentInfo struct ContentInfo { - var thumbnails = [String: [Int: URL]]() + var thumbnails = [String: [Int: String]]() var mpvKeys = [String: String]() var mpvImageKeys = [String: [Int: String]]() var mpvReloadTokens = [String: [Int: ReloadToken]]() @@ -271,7 +271,7 @@ extension AppState { new, uniquingKeysWith: { stored, new in replaceExisting ? new : stored } ) } - mutating func update(gid: String, thumbnails: [Int: URL]) { + mutating func update(gid: String, thumbnails: [Int: String]) { update(gid: gid, stored: &self.thumbnails, new: thumbnails) } mutating func update(gid: String, contents: [Int: String]) { diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index 8f7cf36e..015c4952 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -652,7 +652,7 @@ final class Store: ObservableObject { let batchRange = appState.detailInfo.previewConfig.batchRange(index: index) switch result { case .success(let thumbnails): - let thumbnailURL = thumbnails[index] + let thumbnailURL = thumbnails[index]?.safeURL() if thumbnailURL?.pathComponents.count ?? 0 >= 1, thumbnailURL?.pathComponents[1] == "mpv" { dispatch(.fetchMPVKeys(gid: gid, index: index, mpvURL: thumbnailURL?.absoluteString ?? "")) } else { diff --git a/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataClass.swift b/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataClass.swift index 53125eab..836ae523 100644 --- a/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataClass.swift +++ b/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataClass.swift @@ -18,7 +18,7 @@ extension GalleryStateMO: ManagedObjectProtocol { previews: previews?.toObject() ?? [Int: String](), comments: comments?.toObject() ?? [GalleryComment](), contents: contents?.toObject() ?? [Int: String](), - thumbnails: thumbnails?.toObject() ?? [Int: URL]() + thumbnails: thumbnails?.toObject() ?? [Int: String]() ) } } diff --git a/EhPanda/Database/PersistenceAccessor.swift b/EhPanda/Database/PersistenceAccessor.swift index efcc652e..142d5aa0 100644 --- a/EhPanda/Database/PersistenceAccessor.swift +++ b/EhPanda/Database/PersistenceAccessor.swift @@ -181,10 +181,10 @@ extension PersistenceController { galleryStateMO.readingProgress = Int64(readingProgress) } } - static func update(gid: String, thumbnails: [Int: URL]) { + static func update(gid: String, thumbnails: [Int: String]) { update(gid: gid) { galleryStateMO in guard !thumbnails.isEmpty else { return } - if let storedThumbnails = galleryStateMO.thumbnails?.toObject() as [Int: URL]? { + if let storedThumbnails = galleryStateMO.thumbnails?.toObject() as [Int: String]? { galleryStateMO.thumbnails = storedThumbnails.merging( thumbnails, uniquingKeysWith: { _, new in new } ).toData() diff --git a/EhPanda/Models/Models.swift b/EhPanda/Models/Models.swift index dde3e0e6..d12c83f2 100644 --- a/EhPanda/Models/Models.swift +++ b/EhPanda/Models/Models.swift @@ -119,7 +119,7 @@ struct GalleryState: Codable { var previewConfig: PreviewConfig? var comments = [GalleryComment]() var contents = [Int: String]() - var thumbnails = [Int: URL]() + var thumbnails = [Int: String]() } struct GalleryArchive: Codable { diff --git a/EhPanda/Network/Request.swift b/EhPanda/Network/Request.swift index f32fd50f..ddcd880e 100644 --- a/EhPanda/Network/Request.swift +++ b/EhPanda/Network/Request.swift @@ -487,7 +487,7 @@ struct MPVKeysRequest { struct ThumbnailsRequest { let url: String - var publisher: AnyPublisher<[Int: URL], AppError> { + var publisher: AnyPublisher<[Int: String], AppError> { URLSession.shared.dataTaskPublisher(for: url.safeURL()) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap(Parser.parseThumbnails).mapError(mapAppError).eraseToAnyPublisher() @@ -495,12 +495,12 @@ struct ThumbnailsRequest { } struct GalleryNormalContentsRequest { - let thumbnails: [Int: URL] + let thumbnails: [Int: String] var publisher: AnyPublisher<[Int: String], AppError> { thumbnails.publisher .flatMap { index, url in - URLSession.shared.dataTaskPublisher(for: url).genericRetry() + URLSession.shared.dataTaskPublisher(for: url.safeURL()).genericRetry() .tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap { try Parser.parseGalleryNormalContent(doc: $0, index: index) } } @@ -518,7 +518,7 @@ struct GalleryNormalContentsRequest { struct GalleryNormalContentRefetchRequest { let index: Int let galleryURL: String - let thumbnailURL: URL? + let thumbnailURL: String? let storedImageURL: String let bypassesSNIFiltering: Bool @@ -532,11 +532,12 @@ struct GalleryNormalContentRefetchRequest { func storedThumbnail() -> AnyPublisher { if let thumbnailURL = thumbnailURL { - return Just(thumbnailURL).setFailureType(to: AppError.self).eraseToAnyPublisher() + return Just(thumbnailURL).compactMap(URL.init).setFailureType(to: AppError.self).eraseToAnyPublisher() } else { return URLSession.shared.dataTaskPublisher(for: galleryURL.safeURL()) .tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) }.tryMap(Parser.parseThumbnails) - .compactMap({ thumbnails in thumbnails[index] }).mapError(mapAppError).eraseToAnyPublisher() + .compactMap({ thumbnails in URL(string: thumbnails[index] ?? "") }) + .mapError(mapAppError).eraseToAnyPublisher() } } From 31d5145251f18dd99193f2f55c5317408917b969 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: Sat, 27 Nov 2021 19:41:05 +0900 Subject: [PATCH 07/12] feat: Favorites sort order #163 --- EhPanda/App/Defaults.swift | 17 ++++++++++++++--- EhPanda/App/Tools/Parser.swift | 16 +++++++++++++++- EhPanda/DataFlow/AppAction.swift | 8 +++++--- EhPanda/DataFlow/AppCommand.swift | 11 ++++++----- EhPanda/DataFlow/AppState.swift | 1 + EhPanda/DataFlow/Store.swift | 10 ++++++---- EhPanda/Models/Misc.swift | 1 + EhPanda/Network/Request.swift | 19 ++++++++++++++----- EhPanda/View/Home/HomeView.swift | 25 ++++++++++++++++++++++++- 9 files changed, 86 insertions(+), 22 deletions(-) diff --git a/EhPanda/App/Defaults.swift b/EhPanda/App/Defaults.swift index f6315133..727aaa1d 100644 --- a/EhPanda/App/Defaults.swift +++ b/EhPanda/App/Defaults.swift @@ -114,10 +114,12 @@ struct Defaults { static let loginAct = "act=Login" static let addfavAct = "act=addfav" static let ignoreOffensive = "nw=always" + static let rowsLimit = "inline_set=tr_" static let listCompact = "inline_set=dm_l" static let previewNormal = "inline_set=ts_m" static let previewLarge = "inline_set=ts_l" - static let rowsLimit = "inline_set=tr_4" + static let sortOrderByUpdateTime = "inline_set=fs_p" + static let sortOrderByFavoritedTime = "inline_set=fs_f" // Filter static let fCats = "f_cats=" @@ -186,16 +188,25 @@ extension Defaults.URL { static func moreWatchedList(pageNum: Int, lastID: String) -> String { merge(urls: [host + watched, page2 + String(pageNum), from + lastID]) } - static func favoritesList(favIndex: Int, pageNum: Int? = nil) -> String { + static func favoritesList(favIndex: Int, pageNum: Int? = nil, sortOrder: FavoritesSortOrder? = nil) -> String { var params = [host + favorites] if favIndex == -1 { - if pageNum == nil { return params[0] } + if pageNum == nil { + guard let sortOrder = sortOrder else { + return params[0] + } + params.append(sortOrder == .favoritedTime ? sortOrderByFavoritedTime : sortOrderByUpdateTime) + return merge(urls: params) + } } else { params.append(favcat + "\(favIndex)") } if let pageNum = pageNum { params.append(page2 + String(pageNum)) } + if let sortOrder = sortOrder { + params.append(sortOrder == .favoritedTime ? sortOrderByFavoritedTime : sortOrderByUpdateTime) + } return merge(urls: params) } static func moreFavoritesList(favIndex: Int, pageNum: Int, lastID: String) -> String { diff --git a/EhPanda/App/Tools/Parser.swift b/EhPanda/App/Tools/Parser.swift index 124d2458..51b30a1e 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -1179,7 +1179,7 @@ extension Parser { return (rating, try? parseTextRating(node: node), containsUserRating) } - // MARK: Page Number + // MARK: PageNumber static func parsePageNum(doc: HTMLDocument) -> PageNumber { var current = 0 var maximum = 0 @@ -1201,6 +1201,20 @@ extension Parser { return PageNumber(current: current, maximum: maximum) } + // MARK: SortOrder + static func parseFavoritesSortOrder(doc: HTMLDocument) -> FavoritesSortOrder? { + guard let idoNode = doc.at_xpath("//div [@class='ido']") else { return nil } + for link in idoNode.xpath("//div") where link.className == nil { + guard let aText = link.at_xpath("//div")?.at_xpath("//a")?.text else { continue } + if aText == "Use Posted" { + return .favoritedTime + } else if aText == "Use Favorited" { + return .lastUpdateTime + } + } + return nil + } + // MARK: Balance static func parseCurrentFunds(doc: HTMLDocument) throws -> (String, String)? { var tmpGP: String? diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index 3977aece..fd5107da 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -10,6 +10,7 @@ import Kanna import Foundation enum AppAction { + // swiftlint:disable line_length case resetUser case resetFilters case resetHomeInfo @@ -67,10 +68,10 @@ enum AppAction { case fetchWatchedItemsDone(result: Result<(PageNumber, [Gallery]), AppError>) case fetchMoreWatchedItems case fetchMoreWatchedItemsDone(result: Result<(PageNumber, [Gallery]), AppError>) - case fetchFavoritesItems(pageNum: Int? = nil) - case fetchFavoritesItemsDone(carriedValue: Int, result: Result<(PageNumber, [Gallery]), AppError>) + case fetchFavoritesItems(pageNum: Int? = nil, sortOrder: FavoritesSortOrder? = nil) + case fetchFavoritesItemsDone(carriedValue: Int, result: Result<(PageNumber, FavoritesSortOrder?, [Gallery]), AppError>) case fetchMoreFavoritesItems - case fetchMoreFavoritesItemsDone(carriedValue: Int, result: Result<(PageNumber, [Gallery]), AppError>) + case fetchMoreFavoritesItemsDone(carriedValue: Int, result: Result<(PageNumber, FavoritesSortOrder?, [Gallery]), AppError>) case fetchToplistsItems(pageNum: Int? = nil) case fetchToplistsItemsDone(carriedValue: Int, result: Result<(PageNumber, [Gallery]), AppError>) case fetchMoreToplistsItems @@ -101,4 +102,5 @@ enum AppAction { case commentGallery(gid: String, content: String) case editGalleryComment(gid: String, commentID: String, content: String) case voteGalleryComment(gid: String, commentID: String, vote: Int) + // swiftlint:enable line_length } diff --git a/EhPanda/DataFlow/AppCommand.swift b/EhPanda/DataFlow/AppCommand.swift index aabf43bb..d3ae128f 100644 --- a/EhPanda/DataFlow/AppCommand.swift +++ b/EhPanda/DataFlow/AppCommand.swift @@ -306,20 +306,21 @@ struct FetchMoreWatchedItemsCommand: AppCommand { struct FetchFavoritesItemsCommand: AppCommand { let favIndex: Int var pageNum: Int? + var sortOrder: FavoritesSortOrder? func execute(in store: Store) { let token = SubscriptionToken() - FavoritesItemsRequest(favIndex: favIndex, pageNum: pageNum) + FavoritesItemsRequest(favIndex: favIndex, pageNum: pageNum, sortOrder: sortOrder) .publisher.receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { store.dispatch(.fetchFavoritesItemsDone(carriedValue: favIndex, result: .failure(error))) } token.unseal() - } receiveValue: { (pageNumber, galleries) in + } receiveValue: { (pageNumber, sortOrder, galleries) in if !galleries.isEmpty { store.dispatch(.fetchFavoritesItemsDone( - carriedValue: favIndex, result: .success((pageNumber, galleries))) + carriedValue: favIndex, result: .success((pageNumber, sortOrder, galleries))) ) } else { store.dispatch(.fetchFavoritesItemsDone(carriedValue: favIndex, result: .failure(.notFound))) @@ -347,9 +348,9 @@ struct FetchMoreFavoritesItemsCommand: AppCommand { store.dispatch(.fetchMoreFavoritesItemsDone(carriedValue: favIndex, result: .failure(error))) } token.unseal() - } receiveValue: { (pageNumber, galleries) in + } receiveValue: { (pageNumber, sortOrder, galleries) in store.dispatch(.fetchMoreFavoritesItemsDone( - carriedValue: favIndex, result: .success((pageNumber, galleries))) + carriedValue: favIndex, result: .success((pageNumber, sortOrder, galleries))) ) guard galleries.isEmpty, pageNumber.current < pageNumber.maximum else { return } DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { diff --git a/EhPanda/DataFlow/AppState.swift b/EhPanda/DataFlow/AppState.swift index 4650a542..5c815981 100644 --- a/EhPanda/DataFlow/AppState.swift +++ b/EhPanda/DataFlow/AppState.swift @@ -26,6 +26,7 @@ extension AppState { var slideMenuClosed = true var navigationBarHidden = false var favoritesIndex = -1 + var favoritesSortOrder: FavoritesSortOrder? var toplistsType: ToplistsType = .allTime var homeListType: HomeListType = .frontpage var homeViewSheetState: HomeViewSheetState? diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index 015c4952..dc3a755b 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -437,7 +437,7 @@ final class Store: ObservableObject { appState.homeInfo.moreWatchedLoadFailed = true } - case .fetchFavoritesItems(let pageNum): + case .fetchFavoritesItems(let pageNum, let sortOrder): let favIndex = appState.environment.favoritesIndex appState.homeInfo.favoritesLoadErrors[favIndex] = nil @@ -447,14 +447,15 @@ final class Store: ObservableObject { } appState.homeInfo.favoritesPageNumbers[favIndex]?.current = 0 appState.homeInfo.favoritesLoading[favIndex] = true - appCommand = FetchFavoritesItemsCommand(favIndex: favIndex, pageNum: pageNum) + appCommand = FetchFavoritesItemsCommand(favIndex: favIndex, pageNum: pageNum, sortOrder: sortOrder) case .fetchFavoritesItemsDone(let carriedValue, let result): appState.homeInfo.favoritesLoading[carriedValue] = false switch result { - case .success(let (pageNumber, galleries)): + case .success(let (pageNumber, sortOrder, galleries)): appState.homeInfo.favoritesPageNumbers[carriedValue] = pageNumber appState.homeInfo.favoritesItems[carriedValue] = galleries + appState.environment.favoritesSortOrder = sortOrder PersistenceController.add(galleries: galleries) case .failure(let error): appState.homeInfo.favoritesLoadErrors[carriedValue] = error @@ -481,9 +482,10 @@ final class Store: ObservableObject { appState.homeInfo.moreFavoritesLoading[carriedValue] = false switch result { - case .success(let (pageNumber, galleries)): + case .success(let (pageNumber, sortOrder, galleries)): appState.homeInfo.favoritesPageNumbers[carriedValue] = pageNumber appState.homeInfo.insertFavoritesItems(favIndex: carriedValue, galleries: galleries) + appState.environment.favoritesSortOrder = sortOrder PersistenceController.add(galleries: galleries) case .failure: appState.homeInfo.moreFavoritesLoading[carriedValue] = true diff --git a/EhPanda/Models/Misc.swift b/EhPanda/Models/Misc.swift index 44ff3b07..673339f9 100644 --- a/EhPanda/Models/Misc.swift +++ b/EhPanda/Models/Misc.swift @@ -14,6 +14,7 @@ typealias APIKey = String typealias CurrentGP = String typealias CurrentCredits = String typealias ReloadToken = Any +typealias FavoritesSortOrder = EhSettingFavoritesSortOrder struct PageNumber: Equatable { var current = 0 diff --git a/EhPanda/Network/Request.swift b/EhPanda/Network/Request.swift index ddcd880e..628cc900 100644 --- a/EhPanda/Network/Request.swift +++ b/EhPanda/Network/Request.swift @@ -268,13 +268,18 @@ struct MoreWatchedItemsRequest { struct FavoritesItemsRequest { let favIndex: Int var pageNum: Int? + var sortOrder: FavoritesSortOrder? - var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { + var publisher: AnyPublisher<(PageNumber, FavoritesSortOrder?, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher( - for: Defaults.URL.favoritesList(favIndex: favIndex, pageNum: pageNum).safeURL() + for: Defaults.URL.favoritesList(favIndex: favIndex, pageNum: pageNum, sortOrder: sortOrder).safeURL() ) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } - .tryMap { (Parser.parsePageNum(doc: $0), try Parser.parseListItems(doc: $0)) } + .tryMap { ( + Parser.parsePageNum(doc: $0), + Parser.parseFavoritesSortOrder(doc: $0), + try Parser.parseListItems(doc: $0) + ) } .mapError(mapAppError).eraseToAnyPublisher() } } @@ -284,12 +289,16 @@ struct MoreFavoritesItemsRequest { let lastID: String let pageNum: Int - var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { + var publisher: AnyPublisher<(PageNumber, FavoritesSortOrder?, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher(for: Defaults.URL.moreFavoritesList( favIndex: favIndex, pageNum: pageNum, lastID: lastID ).safeURL()) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } - .tryMap { (Parser.parsePageNum(doc: $0), try Parser.parseListItems(doc: $0)) } + .tryMap { ( + Parser.parsePageNum(doc: $0), + Parser.parseFavoritesSortOrder(doc: $0), + try Parser.parseListItems(doc: $0) + ) } .mapError(mapAppError).eraseToAnyPublisher() } } diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index a63805f1..03dfba73 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -117,6 +117,7 @@ private extension HomeView { if environment.homeListType == .favorites { ForEach(-1..<10) { index in Button { + guard index != environment.favoritesIndex else { return } store.dispatch(.setFavoritesIndex(index)) } label: { Text(User.getFavNameFrom(index: index, names: favoriteNames)) @@ -128,6 +129,7 @@ private extension HomeView { } else if environment.homeListType == .toplists { ForEach(ToplistsType.allCases) { type in Button { + guard type != environment.toplistsType else { return } store.dispatch(.setToplistsType(type)) } label: { Text(type.description.localized) @@ -138,12 +140,32 @@ private extension HomeView { } } } label: { - Image(systemName: "square.3.stack.3d.top.fill") + Image(systemName: "dial.min") .symbolRenderingMode(.hierarchical) .foregroundColor(.primary) } .opacity([.favorites, .toplists].contains(environment.homeListType) ? 1 : 0) } + func sortOrderMenu() -> some View { + Menu { + ForEach(FavoritesSortOrder.allCases) { order in + Button { + guard order != environment.favoritesSortOrder else { return } + store.dispatch(.fetchFavoritesItems(sortOrder: order)) + } label: { + Text(order.value.localized) + if order == environment.favoritesSortOrder { + Image(systemName: "checkmark") + } + } + } + } label: { + Image(systemName: "arrow.up.arrow.down.circle") + .symbolRenderingMode(.hierarchical) + .foregroundColor(.primary) + } + .opacity(environment.homeListType == .favorites ? 1 : 0) + } func moreFeaturesMenu() -> some View { Menu { Button { @@ -190,6 +212,7 @@ private extension HomeView { ToolbarItem(placement: .navigationBarTrailing) { HStack { selectIndexMenu() + sortOrderMenu() moreFeaturesMenu() } } From d330384da23e385697b080f644e0f00a57c6e2d6 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: Sun, 28 Nov 2021 09:55:56 +0900 Subject: [PATCH 08/12] refactor: Update database model --- EhPanda.xcodeproj/project.pbxproj | 4 +- EhPanda/App/Tools/AppEnvStorage.swift | 8 +-- EhPanda/DataFlow/AppAction.swift | 2 +- EhPanda/DataFlow/AppState.swift | 7 +- EhPanda/DataFlow/Store.swift | 8 +-- EhPanda/DataFlow/StoreAccessor.swift | 7 +- .../MODefinition/AppEnvMO+CoreDataClass.swift | 9 ++- .../AppEnvMO+CoreDataProperties.swift | 3 +- .../GalleryStateMO+CoreDataClass.swift | 2 + .../GalleryStateMO+CoreDataProperties.swift | 1 + .../Model.xcdatamodeld/.xccurrentversion | 2 +- .../Model 5.xcdatamodel/contents | 64 +++++++++++++++++++ EhPanda/Models/Models.swift | 1 + EhPanda/View/Detail/AssociatedView.swift | 4 +- EhPanda/View/Home/FilterView.swift | 60 ++++++++--------- 15 files changed, 129 insertions(+), 53 deletions(-) create mode 100644 EhPanda/Database/Model.xcdatamodeld/Model 5.xcdatamodel/contents diff --git a/EhPanda.xcodeproj/project.pbxproj b/EhPanda.xcodeproj/project.pbxproj index c4424c03..19de3b7b 100644 --- a/EhPanda.xcodeproj/project.pbxproj +++ b/EhPanda.xcodeproj/project.pbxproj @@ -228,6 +228,7 @@ ABC3C76E2593699A00E0C11B /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; ABC3C7762593699A00E0C11B /* ViewModifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = ""; }; ABC3C7802593699A00E0C11B /* Models.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; + ABC4A07A2753084100968A4F /* Model 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 5.xcdatamodel"; sourceTree = ""; }; ABC681F226898D46007BBD69 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; ABCA93BD26918DE100A98BC6 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; ABCA93BF2691925900A98BC6 /* GalleryMO+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GalleryMO+CoreDataClass.swift"; sourceTree = ""; }; @@ -1449,12 +1450,13 @@ ABC681F126898D46007BBD69 /* Model.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + ABC4A07A2753084100968A4F /* Model 5.xcdatamodel */, ABE9401626FF2E610085E158 /* Model 4.xcdatamodel */, AB543FF126DB7FD9009344C0 /* Model 3.xcdatamodel */, AB48BCF626D2539B0021A06C /* Model 2.xcdatamodel */, ABC681F226898D46007BBD69 /* Model.xcdatamodel */, ); - currentVersion = ABE9401626FF2E610085E158 /* Model 4.xcdatamodel */; + currentVersion = ABC4A07A2753084100968A4F /* Model 5.xcdatamodel */; path = Model.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/EhPanda/App/Tools/AppEnvStorage.swift b/EhPanda/App/Tools/AppEnvStorage.swift index 6a3499a8..ffed37e5 100644 --- a/EhPanda/App/Tools/AppEnvStorage.swift +++ b/EhPanda/App/Tools/AppEnvStorage.swift @@ -23,9 +23,7 @@ struct AppEnvStorage { return value } } - SwiftyBeaver.error( - "Failed in force downcasting to generic type..." - ) + SwiftyBeaver.error("Failed in force downcasting to generic type...") return nil } @@ -45,9 +43,7 @@ struct AppEnvStorage { if let key = key { self.key = key } else { - self.key = String( - describing: type - ).lowercased() + self.key = String(describing: type).lowercased() } value = fetchedValue } diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index fd5107da..e66bd82e 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -12,7 +12,7 @@ import Foundation enum AppAction { // swiftlint:disable line_length case resetUser - case resetFilters + case resetSearchFilter case resetHomeInfo case setReadingProgress(gid: String, tag: Int) case setAppIconType(_ iconType: IconType) diff --git a/EhPanda/DataFlow/AppState.swift b/EhPanda/DataFlow/AppState.swift index 5c815981..54779d36 100644 --- a/EhPanda/DataFlow/AppState.swift +++ b/EhPanda/DataFlow/AppState.swift @@ -52,8 +52,11 @@ extension AppState { @AppEnvStorage(type: User.self) var user: User - @AppEnvStorage(type: Filter.self) - var filter: Filter + @AppEnvStorage(type: Filter.self, key: "searchFilter") + var searchFilter: Filter + + @AppEnvStorage(type: Filter.self, key: "globalFilter") + var globalFilter: Filter @AppEnvStorage(type: Setting.self) var setting: Setting diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index dc3a755b..9ea8b8bc 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -95,8 +95,8 @@ final class Store: ObservableObject { // MARK: App Ops case .resetUser: appState.settings.user = User() - case .resetFilters: - appState.settings.filter = Filter() + case .resetSearchFilter: + appState.settings.searchFilter = Filter() case .resetHomeInfo: appState.homeInfo = AppState.HomeInfo() dispatch(.setHomeListType(.frontpage)) @@ -292,7 +292,7 @@ final class Store: ObservableObject { appState.homeInfo.searchPageNumber.current = 0 appState.homeInfo.searchLoading = true - let filter = appState.settings.filter + let filter = appState.settings.searchFilter appCommand = FetchSearchItemsCommand(keyword: keyword, filter: filter, pageNum: pageNum) case .fetchSearchItemsDone(let result): appState.homeInfo.searchLoading = false @@ -316,7 +316,7 @@ final class Store: ObservableObject { appState.homeInfo.moreSearchLoading = true let pageNum = pageNumber.current + 1 - let filter = appState.settings.filter + let filter = appState.settings.searchFilter let lastID = appState.homeInfo.searchItems.last?.id ?? "" appCommand = FetchMoreSearchItemsCommand( keyword: keyword, filter: filter, diff --git a/EhPanda/DataFlow/StoreAccessor.swift b/EhPanda/DataFlow/StoreAccessor.swift index 25ff5894..0001297e 100644 --- a/EhPanda/DataFlow/StoreAccessor.swift +++ b/EhPanda/DataFlow/StoreAccessor.swift @@ -66,8 +66,11 @@ extension StoreAccessor { var setting: Setting { settings.setting } - var filter: Filter { - settings.filter + var searchFilter: Filter { + settings.searchFilter + } + var globalFilter: Filter { + settings.globalFilter } var accentColor: Color { setting.accentColor diff --git a/EhPanda/Database/MODefinition/AppEnvMO+CoreDataClass.swift b/EhPanda/Database/MODefinition/AppEnvMO+CoreDataClass.swift index 1834c962..c6404b76 100644 --- a/EhPanda/Database/MODefinition/AppEnvMO+CoreDataClass.swift +++ b/EhPanda/Database/MODefinition/AppEnvMO+CoreDataClass.swift @@ -13,8 +13,9 @@ extension AppEnvMO: ManagedObjectProtocol { func toEntity() -> AppEnv { AppEnv( user: user?.toObject() ?? User(), - filter: filter?.toObject() ?? Filter(), setting: setting?.toObject() ?? Setting(), + searchFilter: searchFilter?.toObject() ?? Filter(), + globalFilter: globalFilter?.toObject() ?? Filter(), tagTranslator: tagTranslator?.toObject() ?? TagTranslator(), historyKeywords: historyKeywords?.toObject() ?? [String](), quickSearchWords: quickSearchWords?.toObject() ?? [QuickSearchWord]() @@ -28,8 +29,9 @@ extension AppEnv: ManagedObjectConvertible { let appEnvMO = AppEnvMO(context: context) appEnvMO.user = user.toData() - appEnvMO.filter = filter.toData() appEnvMO.setting = setting.toData() + appEnvMO.searchFilter = searchFilter.toData() + appEnvMO.globalFilter = globalFilter.toData() appEnvMO.tagTranslator = tagTranslator.toData() appEnvMO.historyKeywords = historyKeywords.toData() appEnvMO.quickSearchWords = quickSearchWords.toData() @@ -40,8 +42,9 @@ extension AppEnv: ManagedObjectConvertible { struct AppEnv: Codable { let user: User - let filter: Filter let setting: Setting + let searchFilter: Filter + let globalFilter: Filter let tagTranslator: TagTranslator let historyKeywords: [String] let quickSearchWords: [QuickSearchWord] diff --git a/EhPanda/Database/MODefinition/AppEnvMO+CoreDataProperties.swift b/EhPanda/Database/MODefinition/AppEnvMO+CoreDataProperties.swift index 4bcd4feb..39569a65 100644 --- a/EhPanda/Database/MODefinition/AppEnvMO+CoreDataProperties.swift +++ b/EhPanda/Database/MODefinition/AppEnvMO+CoreDataProperties.swift @@ -13,8 +13,9 @@ extension AppEnvMO { } @NSManaged public var user: Data? - @NSManaged public var filter: Data? @NSManaged public var setting: Data? + @NSManaged public var searchFilter: Data? + @NSManaged public var globalFilter: Data? @NSManaged public var tagTranslator: Data? @NSManaged public var historyKeywords: Data? @NSManaged public var quickSearchWords: Data? diff --git a/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataClass.swift b/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataClass.swift index 836ae523..7eebe80d 100644 --- a/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataClass.swift +++ b/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataClass.swift @@ -18,6 +18,7 @@ extension GalleryStateMO: ManagedObjectProtocol { previews: previews?.toObject() ?? [Int: String](), comments: comments?.toObject() ?? [GalleryComment](), contents: contents?.toObject() ?? [Int: String](), + originalContents: originalContents?.toObject() ?? [Int: String](), thumbnails: thumbnails?.toObject() ?? [Int: String]() ) } @@ -34,6 +35,7 @@ extension GalleryState: ManagedObjectConvertible { galleryStateMO.previews = previews.toData() galleryStateMO.comments = comments.toData() galleryStateMO.contents = contents.toData() + galleryStateMO.originalContents = originalContents.toData() galleryStateMO.thumbnails = thumbnails.toData() return galleryStateMO diff --git a/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataProperties.swift b/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataProperties.swift index 39de4bf5..dd14249d 100644 --- a/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataProperties.swift +++ b/EhPanda/Database/MODefinition/GalleryStateMO+CoreDataProperties.swift @@ -14,6 +14,7 @@ extension GalleryStateMO: GalleryIdentifiable { @NSManaged public var comments: Data? @NSManaged public var contents: Data? + @NSManaged public var originalContents: Data? @NSManaged public var gid: String @NSManaged public var previews: Data? @NSManaged public var readingProgress: Int64 diff --git a/EhPanda/Database/Model.xcdatamodeld/.xccurrentversion b/EhPanda/Database/Model.xcdatamodeld/.xccurrentversion index ea1cf0bc..fe638601 100644 --- a/EhPanda/Database/Model.xcdatamodeld/.xccurrentversion +++ b/EhPanda/Database/Model.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Model 4.xcdatamodel + Model 5.xcdatamodel diff --git a/EhPanda/Database/Model.xcdatamodeld/Model 5.xcdatamodel/contents b/EhPanda/Database/Model.xcdatamodeld/Model 5.xcdatamodel/contents new file mode 100644 index 00000000..8cc55b7f --- /dev/null +++ b/EhPanda/Database/Model.xcdatamodeld/Model 5.xcdatamodel/contents @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EhPanda/Models/Models.swift b/EhPanda/Models/Models.swift index d12c83f2..85cc2666 100644 --- a/EhPanda/Models/Models.swift +++ b/EhPanda/Models/Models.swift @@ -119,6 +119,7 @@ struct GalleryState: Codable { var previewConfig: PreviewConfig? var comments = [GalleryComment]() var contents = [Int: String]() + var originalContents = [Int: String]() var thumbnails = [Int: String]() } diff --git a/EhPanda/View/Detail/AssociatedView.swift b/EhPanda/View/Detail/AssociatedView.swift index 03acb7ed..ba54b301 100644 --- a/EhPanda/View/Detail/AssociatedView.swift +++ b/EhPanda/View/Detail/AssociatedView.swift @@ -130,7 +130,7 @@ private extension AssociatedView { let token = SubscriptionToken() SearchItemsRequest( keyword: keyword.isEmpty ? title : keyword, - filter: filter, pageNum: pageNum + filter: searchFilter, pageNum: pageNum ) .publisher.receive(on: DispatchQueue.main) .sink { completion in @@ -188,7 +188,7 @@ private extension AssociatedView { let token = SubscriptionToken() MoreSearchItemsRequest( - keyword: keyword.isEmpty ? title : keyword, filter: filter, + keyword: keyword.isEmpty ? title : keyword, filter: searchFilter, lastID: lastID, pageNum: pageNumber.current + 1 ) .publisher.receive(on: DispatchQueue.main) diff --git a/EhPanda/View/Home/FilterView.swift b/EhPanda/View/Home/FilterView.swift index 3a890477..e9107980 100644 --- a/EhPanda/View/Home/FilterView.swift +++ b/EhPanda/View/Home/FilterView.swift @@ -11,17 +11,17 @@ struct FilterView: View, StoreAccessor { @EnvironmentObject var store: Store @State var resetDialogPresented = false - private var categoryBindings: [Binding] { + private var searchCategoryBindings: [Binding] { [ - filterBinding.doujinshi, filterBinding.manga, - filterBinding.artistCG, filterBinding.gameCG, - filterBinding.western, filterBinding.nonH, - filterBinding.imageSet, filterBinding.cosplay, - filterBinding.asianPorn, filterBinding.misc + searchFilterBinding.doujinshi, searchFilterBinding.manga, + searchFilterBinding.artistCG, searchFilterBinding.gameCG, + searchFilterBinding.western, searchFilterBinding.nonH, + searchFilterBinding.imageSet, searchFilterBinding.cosplay, + searchFilterBinding.asianPorn, searchFilterBinding.misc ] } - private var filterBinding: Binding { - $store.appState.settings.filter + private var searchFilterBinding: Binding { + $store.appState.settings.searchFilter } // MARK: FilterView @@ -29,43 +29,43 @@ struct FilterView: View, StoreAccessor { NavigationView { Form { Section { - CategoryView(bindings: categoryBindings) + CategoryView(bindings: searchCategoryBindings) Button { resetDialogPresented = true } label: { Text("Reset filters").foregroundStyle(.red) } - Toggle("Advanced settings", isOn: filterBinding.advanced) + Toggle("Advanced settings", isOn: searchFilterBinding.advanced) } Group { Section("Advanced".localized) { - Toggle("Search gallery name", isOn: filterBinding.galleryName) - Toggle("Search gallery tags", isOn: filterBinding.galleryTags) - Toggle("Search gallery description", isOn: filterBinding.galleryDesc) - Toggle("Search torrent filenames", isOn: filterBinding.torrentFilenames) - Toggle("Only show galleries with torrents", isOn: filterBinding.onlyWithTorrents) - Toggle("Search Low-Power tags", isOn: filterBinding.lowPowerTags) - Toggle("Search downvoted tags", isOn: filterBinding.downvotedTags) - Toggle("Show expunged galleries", isOn: filterBinding.expungedGalleries) + Toggle("Search gallery name", isOn: searchFilterBinding.galleryName) + Toggle("Search gallery tags", isOn: searchFilterBinding.galleryTags) + Toggle("Search gallery description", isOn: searchFilterBinding.galleryDesc) + Toggle("Search torrent filenames", isOn: searchFilterBinding.torrentFilenames) + Toggle("Only show galleries with torrents", isOn: searchFilterBinding.onlyWithTorrents) + Toggle("Search Low-Power tags", isOn: searchFilterBinding.lowPowerTags) + Toggle("Search downvoted tags", isOn: searchFilterBinding.downvotedTags) + Toggle("Show expunged galleries", isOn: searchFilterBinding.expungedGalleries) } Section { - Toggle("Set minimum rating", isOn: filterBinding.minRatingActivated) - MinimumRatingSetter(minimum: filterBinding.minRating) - .disabled(!filter.minRatingActivated) - Toggle("Set pages range", isOn: filterBinding.pageRangeActivated) + Toggle("Set minimum rating", isOn: searchFilterBinding.minRatingActivated) + MinimumRatingSetter(minimum: searchFilterBinding.minRating) + .disabled(!searchFilter.minRatingActivated) + Toggle("Set pages range", isOn: searchFilterBinding.pageRangeActivated) PagesRangeSetter( - lowerBound: filterBinding.pageLowerBound, - upperBound: filterBinding.pageUpperBound + lowerBound: searchFilterBinding.pageLowerBound, + upperBound: searchFilterBinding.pageUpperBound ) - .disabled(!filter.pageRangeActivated) + .disabled(!searchFilter.pageRangeActivated) } Section("Default Filter".localized) { - Toggle("Disable language filter", isOn: filterBinding.disableLanguage) - Toggle("Disable uploader filter", isOn: filterBinding.disableUploader) - Toggle("Disable tags filter", isOn: filterBinding.disableTags) + Toggle("Disable language filter", isOn: searchFilterBinding.disableLanguage) + Toggle("Disable uploader filter", isOn: searchFilterBinding.disableUploader) + Toggle("Disable tags filter", isOn: searchFilterBinding.disableTags) } } - .disabled(!filter.advanced) + .disabled(!searchFilter.advanced) } .confirmationDialog( "Are you sure to reset?", @@ -73,7 +73,7 @@ struct FilterView: View, StoreAccessor { titleVisibility: .visible ) { Button("Reset", role: .destructive) { - store.dispatch(.resetFilters) + store.dispatch(.resetSearchFilter) } } .navigationBarTitle("Filters") From cffda391693d4cc4405f8c6cbb79ee257328a7c8 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: Sun, 28 Nov 2021 11:23:05 +0900 Subject: [PATCH 09/12] feat: Global filters #181 --- EhPanda/App/Defaults.swift | 37 +++-- EhPanda/DataFlow/AppAction.swift | 2 +- EhPanda/DataFlow/AppCommand.swift | 15 +- EhPanda/DataFlow/Store.swift | 32 ++-- EhPanda/Models/Filter.swift | 8 +- EhPanda/Network/Request.swift | 16 +- EhPanda/View/Home/FilterView.swift | 152 +++++++++++------- EhPanda/View/Home/HomeView.swift | 9 +- EhPanda/View/Setting/AccountSettingView.swift | 15 +- EhPanda/View/Setting/EhSettingView.swift | 4 +- EhPanda/View/Setting/GeneralSettingView.swift | 4 +- 11 files changed, 169 insertions(+), 125 deletions(-) diff --git a/EhPanda/App/Defaults.swift b/EhPanda/App/Defaults.swift index 727aaa1d..3993828b 100644 --- a/EhPanda/App/Defaults.swift +++ b/EhPanda/App/Defaults.swift @@ -165,35 +165,35 @@ extension Defaults.URL { + applyFilters(filter: filter) ) } - static func frontpageList(pageNum: Int? = nil) -> String { + static func frontpageList(filter: Filter, pageNum: Int? = nil) -> String { if let pageNum = pageNum { - return merge(urls: [host, page2 + String(pageNum)]) + return merge(urls: [host, page2 + String(pageNum)] + applyFilters(filter: filter)) } else { - return host + return merge(urls: [host] + applyFilters(filter: filter)) } } - static func moreFrontpageList(pageNum: Int, lastID: String) -> String { - merge(urls: [host, page2 + String(pageNum), from + lastID]) + static func moreFrontpageList(filter: Filter, pageNum: Int, lastID: String) -> String { + merge(urls: [host, page2 + String(pageNum), from + lastID] + applyFilters(filter: filter)) } - static func popularList() -> String { - host + popular + static func popularList(filter: Filter) -> String { + merge(urls: [host + popular] + applyFilters(filter: filter)) } - static func watchedList(pageNum: Int? = nil) -> String { + static func watchedList(filter: Filter, pageNum: Int? = nil) -> String { if let pageNum = pageNum { return merge(urls: [host + watched, page2 + String(pageNum)]) } else { - return host + watched + return merge(urls: [host + watched] + applyFilters(filter: filter)) } } - static func moreWatchedList(pageNum: Int, lastID: String) -> String { - merge(urls: [host + watched, page2 + String(pageNum), from + lastID]) + static func moreWatchedList(filter: Filter, pageNum: Int, lastID: String) -> String { + merge(urls: [host + watched, page2 + String(pageNum), from + lastID] + applyFilters(filter: filter)) } static func favoritesList(favIndex: Int, pageNum: Int? = nil, sortOrder: FavoritesSortOrder? = nil) -> String { var params = [host + favorites] if favIndex == -1 { if pageNum == nil { guard let sortOrder = sortOrder else { - return params[0] + return merge(urls: params) } params.append(sortOrder == .favoritedTime ? sortOrderByFavoritedTime : sortOrderByUpdateTime) return merge(urls: params) @@ -307,17 +307,14 @@ private extension Defaults.URL { if filter.downvotedTags { filters.append(fSdt2On) } if filter.expungedGalleries { filters.append(fShOn) } - if filter.minRatingActivated, - [2, 3, 4, 5].contains(filter.minRating) - { + if filter.minRatingActivated, [2, 3, 4, 5].contains(filter.minRating) { filters.append(fSrOn) filters.append(fSrdd + "\(filter.minRating)") } - if filter.pageRangeActivated, - let minPages = Int(filter.pageLowerBound), - let maxPages = Int(filter.pageUpperBound), - minPages > 0 && maxPages > 0 && minPages <= maxPages + if filter.pageRangeActivated, let minPages = Int(filter.pageLowerBound), + let maxPages = Int(filter.pageUpperBound), + minPages > 0 && maxPages > 0 && minPages <= maxPages { filters.append(fSpOn) filters.append(fSpf + "\(minPages)") @@ -345,6 +342,8 @@ extension Defaults.URL { // MARK: Tools private extension Defaults.URL { static func merge(urls: [String]) -> String { + guard !urls.isEmpty else { return "" } + guard urls.count > 1 else { return urls[0] } let firstTwo = urls.prefix(2) let remainder = urls.suffix(from: 2) diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index e66bd82e..f7bcc5c2 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -12,8 +12,8 @@ import Foundation enum AppAction { // swiftlint:disable line_length case resetUser - case resetSearchFilter case resetHomeInfo + case resetFilter(range: FilterRange) case setReadingProgress(gid: String, tag: Int) case setAppIconType(_ iconType: IconType) case appendHistoryKeywords(texts: [String]) diff --git a/EhPanda/DataFlow/AppCommand.swift b/EhPanda/DataFlow/AppCommand.swift index d3ae128f..97ddb270 100644 --- a/EhPanda/DataFlow/AppCommand.swift +++ b/EhPanda/DataFlow/AppCommand.swift @@ -177,11 +177,12 @@ struct FetchMoreSearchItemsCommand: AppCommand { } struct FetchFrontpageItemsCommand: AppCommand { + let filter: Filter var pageNum: Int? func execute(in store: Store) { let token = SubscriptionToken() - FrontpageItemsRequest(pageNum: pageNum).publisher + FrontpageItemsRequest(filter: filter, pageNum: pageNum).publisher .receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { @@ -204,12 +205,13 @@ struct FetchFrontpageItemsCommand: AppCommand { } struct FetchMoreFrontpageItemsCommand: AppCommand { + let filter: Filter let lastID: String let pageNum: Int func execute(in store: Store) { let token = SubscriptionToken() - MoreFrontpageItemsRequest(lastID: lastID, pageNum: pageNum) + MoreFrontpageItemsRequest(filter: filter, lastID: lastID, pageNum: pageNum) .publisher.receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { @@ -229,11 +231,12 @@ struct FetchMoreFrontpageItemsCommand: AppCommand { } struct FetchPopularItemsCommand: AppCommand { + let filter: Filter var pageNum: Int? func execute(in store: Store) { let token = SubscriptionToken() - PopularItemsRequest().publisher + PopularItemsRequest(filter: filter).publisher .receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { @@ -252,11 +255,12 @@ struct FetchPopularItemsCommand: AppCommand { } struct FetchWatchedItemsCommand: AppCommand { + let filter: Filter var pageNum: Int? func execute(in store: Store) { let token = SubscriptionToken() - WatchedItemsRequest(pageNum: pageNum).publisher + WatchedItemsRequest(filter: filter, pageNum: pageNum).publisher .receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { @@ -279,12 +283,13 @@ struct FetchWatchedItemsCommand: AppCommand { } struct FetchMoreWatchedItemsCommand: AppCommand { + let filter: Filter let lastID: String let pageNum: Int func execute(in store: Store) { let token = SubscriptionToken() - MoreWatchedItemsRequest(lastID: lastID, pageNum: pageNum) + MoreWatchedItemsRequest(filter: filter, lastID: lastID, pageNum: pageNum) .publisher.receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index 9ea8b8bc..6419c8d1 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -95,12 +95,17 @@ final class Store: ObservableObject { // MARK: App Ops case .resetUser: appState.settings.user = User() - case .resetSearchFilter: - appState.settings.searchFilter = Filter() case .resetHomeInfo: appState.homeInfo = AppState.HomeInfo() dispatch(.setHomeListType(.frontpage)) dispatch(.fetchFrontpageItems(pageNum: nil)) + case .resetFilter(let range): + switch range { + case .search: + appState.settings.searchFilter = Filter() + case .global: + appState.settings.globalFilter = Filter() + } case .setReadingProgress(let gid, let tag): PersistenceController.update(gid: gid, readingProgress: tag) case .setAppIconType(let iconType): @@ -340,7 +345,8 @@ final class Store: ObservableObject { if appState.homeInfo.frontpageLoading { break } appState.homeInfo.frontpagePageNumber.current = 0 appState.homeInfo.frontpageLoading = true - appCommand = FetchFrontpageItemsCommand(pageNum: pageNum) + let filter = appState.settings.globalFilter + appCommand = FetchFrontpageItemsCommand(filter: filter, pageNum: pageNum) case .fetchFrontpageItemsDone(let result): appState.homeInfo.frontpageLoading = false @@ -363,8 +369,9 @@ final class Store: ObservableObject { appState.homeInfo.moreFrontpageLoading = true let pageNum = pageNumber.current + 1 + let filter = appState.settings.globalFilter let lastID = appState.homeInfo.frontpageItems.last?.id ?? "" - appCommand = FetchMoreFrontpageItemsCommand(lastID: lastID, pageNum: pageNum) + appCommand = FetchMoreFrontpageItemsCommand(filter: filter, lastID: lastID, pageNum: pageNum) case .fetchMoreFrontpageItemsDone(let result): appState.homeInfo.moreFrontpageLoading = false @@ -382,7 +389,8 @@ final class Store: ObservableObject { if appState.homeInfo.popularLoading { break } appState.homeInfo.popularLoading = true - appCommand = FetchPopularItemsCommand() + let filter = appState.settings.globalFilter + appCommand = FetchPopularItemsCommand(filter: filter) case .fetchPopularItemsDone(let result): appState.homeInfo.popularLoading = false @@ -400,7 +408,8 @@ final class Store: ObservableObject { if appState.homeInfo.watchedLoading { break } appState.homeInfo.watchedPageNumber.current = 0 appState.homeInfo.watchedLoading = true - appCommand = FetchWatchedItemsCommand(pageNum: pageNum) + let filter = appState.settings.globalFilter + appCommand = FetchWatchedItemsCommand(filter: filter, pageNum: pageNum) case .fetchWatchedItemsDone(let result): appState.homeInfo.watchedLoading = false @@ -423,8 +432,9 @@ final class Store: ObservableObject { appState.homeInfo.moreWatchedLoading = true let pageNum = pageNumber.current + 1 + let filter = appState.settings.globalFilter let lastID = appState.homeInfo.watchedItems.last?.id ?? "" - appCommand = FetchMoreWatchedItemsCommand(lastID: lastID, pageNum: pageNum) + appCommand = FetchMoreWatchedItemsCommand(filter: filter, lastID: lastID, pageNum: pageNum) case .fetchMoreWatchedItemsDone(let result): appState.homeInfo.moreWatchedLoading = false @@ -474,9 +484,7 @@ final class Store: ObservableObject { let pageNum = (pageNumber?.current ?? 0) + 1 let lastID = appState.homeInfo.favoritesItems[favIndex]?.last?.id ?? "" appCommand = FetchMoreFavoritesItemsCommand( - favIndex: favIndex, - lastID: lastID, - pageNum: pageNum + favIndex: favIndex, lastID: lastID, pageNum: pageNum ) case .fetchMoreFavoritesItemsDone(let carriedValue, let result): appState.homeInfo.moreFavoritesLoading[carriedValue] = false @@ -528,9 +536,7 @@ final class Store: ObservableObject { let pageNum = (pageNumber?.current ?? 0) + 1 appCommand = FetchMoreToplistsItemsCommand( - topIndex: topType.rawValue, - catIndex: topType.categoryIndex, - pageNum: pageNum + topIndex: topType.rawValue, catIndex: topType.categoryIndex, pageNum: pageNum ) case .fetchMoreToplistsItemsDone(let carriedValue, let result): appState.homeInfo.moreToplistsLoading[carriedValue] = false diff --git a/EhPanda/Models/Filter.swift b/EhPanda/Models/Filter.swift index 586c5d29..12a3210e 100644 --- a/EhPanda/Models/Filter.swift +++ b/EhPanda/Models/Filter.swift @@ -48,18 +48,14 @@ struct Filter: Codable { @DefaultFalse var pageRangeActivated = false @DefaultStringValue var pageLowerBound = "" { didSet { - if Int(pageLowerBound) == nil - && !pageLowerBound.isEmpty - { + if Int(pageLowerBound) == nil && !pageLowerBound.isEmpty { pageLowerBound = "" } } } @DefaultStringValue var pageUpperBound = "" { didSet { - if Int(pageUpperBound) == nil - && !pageUpperBound.isEmpty - { + if Int(pageUpperBound) == nil && !pageUpperBound.isEmpty { pageUpperBound = "" } } diff --git a/EhPanda/Network/Request.swift b/EhPanda/Network/Request.swift index 628cc900..cb582c3a 100644 --- a/EhPanda/Network/Request.swift +++ b/EhPanda/Network/Request.swift @@ -208,10 +208,11 @@ struct MoreSearchItemsRequest { } struct FrontpageItemsRequest { + let filter: Filter var pageNum: Int? var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { - URLSession.shared.dataTaskPublisher(for: Defaults.URL.frontpageList(pageNum: pageNum).safeURL()) + URLSession.shared.dataTaskPublisher(for: Defaults.URL.frontpageList(filter: filter, pageNum: pageNum).safeURL()) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap { (Parser.parsePageNum(doc: $0), try Parser.parseListItems(doc: $0)) } .mapError(mapAppError).eraseToAnyPublisher() @@ -219,12 +220,13 @@ struct FrontpageItemsRequest { } struct MoreFrontpageItemsRequest { + let filter: Filter let lastID: String let pageNum: Int var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher(for: Defaults.URL.moreFrontpageList( - pageNum: pageNum, lastID: lastID + filter: filter, pageNum: pageNum, lastID: lastID ).safeURL()) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap { (Parser.parsePageNum(doc: $0), try Parser.parseListItems(doc: $0)) } @@ -233,18 +235,21 @@ struct MoreFrontpageItemsRequest { } struct PopularItemsRequest { + let filter: Filter + var publisher: AnyPublisher<[Gallery], AppError> { - URLSession.shared.dataTaskPublisher(for: Defaults.URL.popularList().safeURL()) + URLSession.shared.dataTaskPublisher(for: Defaults.URL.popularList(filter: filter).safeURL()) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap(Parser.parseListItems).mapError(mapAppError).eraseToAnyPublisher() } } struct WatchedItemsRequest { + let filter: Filter var pageNum: Int? var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { - URLSession.shared.dataTaskPublisher(for: Defaults.URL.watchedList(pageNum: pageNum).safeURL()) + URLSession.shared.dataTaskPublisher(for: Defaults.URL.watchedList(filter: filter, pageNum: pageNum).safeURL()) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap { (Parser.parsePageNum(doc: $0), try Parser.parseListItems(doc: $0)) } .mapError(mapAppError).eraseToAnyPublisher() @@ -252,12 +257,13 @@ struct WatchedItemsRequest { } struct MoreWatchedItemsRequest { + let filter: Filter let lastID: String let pageNum: Int var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher(for: Defaults.URL.moreWatchedList( - pageNum: pageNum, lastID: lastID + filter: filter, pageNum: pageNum, lastID: lastID ).safeURL()) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap { (Parser.parsePageNum(doc: $0), try Parser.parseListItems(doc: $0)) } diff --git a/EhPanda/View/Home/FilterView.swift b/EhPanda/View/Home/FilterView.swift index e9107980..580ed3fa 100644 --- a/EhPanda/View/Home/FilterView.swift +++ b/EhPanda/View/Home/FilterView.swift @@ -9,71 +9,30 @@ import SwiftUI struct FilterView: View, StoreAccessor { @EnvironmentObject var store: Store - @State var resetDialogPresented = false - - private var searchCategoryBindings: [Binding] { - [ - searchFilterBinding.doujinshi, searchFilterBinding.manga, - searchFilterBinding.artistCG, searchFilterBinding.gameCG, - searchFilterBinding.western, searchFilterBinding.nonH, - searchFilterBinding.imageSet, searchFilterBinding.cosplay, - searchFilterBinding.asianPorn, searchFilterBinding.misc - ] - } - private var searchFilterBinding: Binding { - $store.appState.settings.searchFilter + @State private var resetDialogPresented = false + @State private var filterRange: FilterRange = .search + + private var filterBinding: Binding { + filterRange == .search + ? $store.appState.settings.searchFilter + : $store.appState.settings.globalFilter } // MARK: FilterView var body: some View { NavigationView { Form { - Section { - CategoryView(bindings: searchCategoryBindings) - Button { - resetDialogPresented = true - } label: { - Text("Reset filters").foregroundStyle(.red) - } - Toggle("Advanced settings", isOn: searchFilterBinding.advanced) - } - Group { - Section("Advanced".localized) { - Toggle("Search gallery name", isOn: searchFilterBinding.galleryName) - Toggle("Search gallery tags", isOn: searchFilterBinding.galleryTags) - Toggle("Search gallery description", isOn: searchFilterBinding.galleryDesc) - Toggle("Search torrent filenames", isOn: searchFilterBinding.torrentFilenames) - Toggle("Only show galleries with torrents", isOn: searchFilterBinding.onlyWithTorrents) - Toggle("Search Low-Power tags", isOn: searchFilterBinding.lowPowerTags) - Toggle("Search downvoted tags", isOn: searchFilterBinding.downvotedTags) - Toggle("Show expunged galleries", isOn: searchFilterBinding.expungedGalleries) - } - Section { - Toggle("Set minimum rating", isOn: searchFilterBinding.minRatingActivated) - MinimumRatingSetter(minimum: searchFilterBinding.minRating) - .disabled(!searchFilter.minRatingActivated) - Toggle("Set pages range", isOn: searchFilterBinding.pageRangeActivated) - PagesRangeSetter( - lowerBound: searchFilterBinding.pageLowerBound, - upperBound: searchFilterBinding.pageUpperBound - ) - .disabled(!searchFilter.pageRangeActivated) - } - Section("Default Filter".localized) { - Toggle("Disable language filter", isOn: searchFilterBinding.disableLanguage) - Toggle("Disable uploader filter", isOn: searchFilterBinding.disableUploader) - Toggle("Disable tags filter", isOn: searchFilterBinding.disableTags) - } - } - .disabled(!searchFilter.advanced) + BasicSection( + filter: filterBinding, filterRange: $filterRange, + resetDialogPresented: $resetDialogPresented + ) + AdvancedSection(filter: filterBinding) } .confirmationDialog( - "Are you sure to reset?", - isPresented: $resetDialogPresented, - titleVisibility: .visible + "Are you sure to reset?", isPresented: $resetDialogPresented, titleVisibility: .visible ) { Button("Reset", role: .destructive) { - store.dispatch(.resetSearchFilter) + store.dispatch(.resetFilter(range: filterRange)) } } .navigationBarTitle("Filters") @@ -81,6 +40,82 @@ struct FilterView: View, StoreAccessor { } } +// MARK: BasicSection +private struct BasicSection: View { + @Binding private var filter: Filter + @Binding private var filterRange: FilterRange + @Binding private var resetDialogPresented: Bool + private var categoryBindings: [Binding] { [ + $filter.doujinshi, $filter.manga, $filter.artistCG, $filter.gameCG, $filter.western, + $filter.nonH, $filter.imageSet, $filter.cosplay, $filter.asianPorn, $filter.misc + ] } + + init(filter: Binding, filterRange: Binding, resetDialogPresented: Binding) { + _filter = filter + _filterRange = filterRange + _resetDialogPresented = resetDialogPresented + } + + var body: some View { + Section { + Picker("Range", selection: $filterRange) { + ForEach(FilterRange.allCases) { range in + Text(range.rawValue.localized).tag(range) + } + } + .pickerStyle(.segmented) + CategoryView(bindings: categoryBindings) + Button { + resetDialogPresented = true + } label: { + Text("Reset filters").foregroundStyle(.red) + } + Toggle("Advanced settings", isOn: $filter.advanced) + } + } +} + +// MARK: AdvancedSection +private struct AdvancedSection: View { + @Binding private var filter: Filter + + init(filter: Binding) { + _filter = filter + } + + var body: some View { + Group { + Section("Advanced".localized) { + Toggle("Search gallery name", isOn: $filter.galleryName) + Toggle("Search gallery tags", isOn: $filter.galleryTags) + Toggle("Search gallery description", isOn: $filter.galleryDesc) + Toggle("Search torrent filenames", isOn: $filter.torrentFilenames) + Toggle("Only show galleries with torrents", isOn: $filter.onlyWithTorrents) + Toggle("Search Low-Power tags", isOn: $filter.lowPowerTags) + Toggle("Search downvoted tags", isOn: $filter.downvotedTags) + Toggle("Show expunged galleries", isOn: $filter.expungedGalleries) + } + Section { + Toggle("Set minimum rating", isOn: $filter.minRatingActivated) + MinimumRatingSetter(minimum: $filter.minRating) + .disabled(!filter.minRatingActivated) + Toggle("Set pages range", isOn: $filter.pageRangeActivated) + PagesRangeSetter( + lowerBound: $filter.pageLowerBound, + upperBound: $filter.pageUpperBound + ) + .disabled(!filter.pageRangeActivated) + } + Section("Default Filter".localized) { + Toggle("Disable language filter", isOn: $filter.disableLanguage) + Toggle("Disable uploader filter", isOn: $filter.disableUploader) + Toggle("Disable tags filter", isOn: $filter.disableTags) + } + } + .disabled(!filter.advanced) + } +} + // MARK: MinimumRatingSetter private struct MinimumRatingSetter: View { @Binding private var minimum: Int @@ -151,3 +186,10 @@ private struct TupleCategory: Identifiable { let isFiltered: Binding let category: Category } + +enum FilterRange: String, CaseIterable, Identifiable { + var id: String { rawValue } + + case search = "Search" + case global = "Global" +} diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index 03dfba73..43d66471 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -81,9 +81,7 @@ struct HomeView: View, StoreAccessor { buttons: [.regular(content: { Text("Confirm") }, action: tryPerformJumpPage)] ) .confirmationDialog( - "Are you sure to clear?", - isPresented: $clearHistoryDialogPresented, - titleVisibility: .visible + "Are you sure to clear?", isPresented: $clearHistoryDialogPresented, titleVisibility: .visible ) { Button("Clear", role: .destructive, action: PersistenceController.clearGalleryHistory) } @@ -164,7 +162,6 @@ private extension HomeView { .symbolRenderingMode(.hierarchical) .foregroundColor(.primary) } - .opacity(environment.homeListType == .favorites ? 1 : 0) } func moreFeaturesMenu() -> some View { Menu { @@ -212,7 +209,9 @@ private extension HomeView { ToolbarItem(placement: .navigationBarTrailing) { HStack { selectIndexMenu() - sortOrderMenu() + if environment.homeListType == .favorites { + sortOrderMenu() + } moreFeaturesMenu() } } diff --git a/EhPanda/View/Setting/AccountSettingView.swift b/EhPanda/View/Setting/AccountSettingView.swift index a25abd27..b0a1da59 100644 --- a/EhPanda/View/Setting/AccountSettingView.swift +++ b/EhPanda/View/Setting/AccountSettingView.swift @@ -29,14 +29,11 @@ struct AccountSettingView: View, StoreAccessor { ZStack { Form { Section { - Picker( - selection: $galleryHost, label: Text("Gallery"), - content: { - ForEach(GalleryHost.allCases) { - Text($0.rawValue.localized).tag($0) - } + Picker("Gallery", selection: $galleryHost) { + ForEach(GalleryHost.allCases) { + Text($0.rawValue.localized).tag($0) } - ) + } .pickerStyle(.segmented) if !AuthorizationUtil.didLogin { NavigationLink("Login", destination: LoginView()).foregroundStyle(.tint) @@ -76,9 +73,7 @@ struct AccountSettingView: View, StoreAccessor { TTProgressHUD($hudVisible, config: hudConfig) } .confirmationDialog( - "Are you sure to logout?", - isPresented: $logoutDialogPresented, - titleVisibility: .visible + "Are you sure to logout?", isPresented: $logoutDialogPresented, titleVisibility: .visible ) { Button("Logout", role: .destructive, action: logout) } diff --git a/EhPanda/View/Setting/EhSettingView.swift b/EhPanda/View/Setting/EhSettingView.swift index cf093a09..841da472 100644 --- a/EhPanda/View/Setting/EhSettingView.swift +++ b/EhPanda/View/Setting/EhSettingView.swift @@ -262,9 +262,7 @@ private struct EhProfileSection: View { } } .confirmationDialog( - "Are you sure to delete this profile?", - isPresented: $dialogPresented, - titleVisibility: .visible + "Are you sure to delete this profile?", isPresented: $dialogPresented, titleVisibility: .visible ) { Button("Delete", role: .destructive) { performEhProfileAction(.default, nil, selection.value) diff --git a/EhPanda/View/Setting/GeneralSettingView.swift b/EhPanda/View/Setting/GeneralSettingView.swift index 78d2433a..57b8d33b 100644 --- a/EhPanda/View/Setting/GeneralSettingView.swift +++ b/EhPanda/View/Setting/GeneralSettingView.swift @@ -82,9 +82,7 @@ struct GeneralSettingView: View, StoreAccessor { } } .confirmationDialog( - "Are you sure to clear?", - isPresented: $clearDialogPresented, - titleVisibility: .visible + "Are you sure to clear?", isPresented: $clearDialogPresented, titleVisibility: .visible ) { Button("Clear", role: .destructive, action: clearImageCaches) } From ad97690834d9163299a7d56d382c89f458c7a2c6 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: Sun, 28 Nov 2021 16:58:04 +0900 Subject: [PATCH 10/12] feat: Save original image #155 --- EhPanda/App/Tools/ImageSaver.swift | 60 ++++++++++++++---- EhPanda/App/Tools/Parser.swift | 8 ++- EhPanda/App/de.lproj/Localizable.strings | 1 + EhPanda/App/ja.lproj/Localizable.strings | 1 + EhPanda/App/ko.lproj/Localizable.strings | 1 + EhPanda/App/zh-Hans.lproj/Localizable.strings | 1 + EhPanda/App/zh-Hant.lproj/Localizable.strings | 1 + EhPanda/DataFlow/AppAction.swift | 4 +- EhPanda/DataFlow/AppCommand.swift | 10 +-- EhPanda/DataFlow/AppState.swift | 8 ++- EhPanda/DataFlow/Store.swift | 25 +++++--- EhPanda/Database/PersistenceAccessor.swift | 25 +++++--- EhPanda/Network/Request.swift | 17 +++-- EhPanda/View/Reading/ReadingView.swift | 62 +++++++++---------- 14 files changed, 145 insertions(+), 79 deletions(-) diff --git a/EhPanda/App/Tools/ImageSaver.swift b/EhPanda/App/Tools/ImageSaver.swift index c73c4c8e..7e4ed10c 100644 --- a/EhPanda/App/Tools/ImageSaver.swift +++ b/EhPanda/App/Tools/ImageSaver.swift @@ -6,28 +6,66 @@ // import SwiftUI +import Kingfisher import SwiftyBeaver -class ImageSaver: NSObject { - @Binding var isSuccess: Bool? +final class ImageSaver: NSObject, ObservableObject { + @Published var saveSucceeded: Bool? - init(isSuccess: Binding) { - _isSuccess = isSuccess + func retrieveImage(url: URL) async throws -> UIImage { + if let cachedImage = try? await retrieveCache(key: url.absoluteString) { + return cachedImage + } else { + do { + return try await downloadImage(url: url) + } catch { + throw error + } + } + } + private func retrieveCache(key: String) async throws -> UIImage { + try await withCheckedThrowingContinuation { continuation in + KingfisherManager.shared.cache.retrieveImage(forKey: key) { result in + switch result { + case .success(let result): + if let image = result.image { + continuation.resume(returning: image) + } else { + continuation.resume(throwing: AppError.notFound) + } + case .failure(let error): + SwiftyBeaver.error(error) + continuation.resume(throwing: error) + } + } + } + } + private func downloadImage(url: URL) async throws -> UIImage { + try await withCheckedThrowingContinuation { continuation in + KingfisherManager.shared.downloader.downloadImage(with: url, options: nil) { result in + switch result { + case .success(let result): + continuation.resume(returning: result.image) + case .failure(let error): + SwiftyBeaver.error(error) + continuation.resume(throwing: error) + } + } + } } func saveImage(_ image: UIImage) { - UIImageWriteToSavedPhotosAlbum( - image, self, #selector(didFinishSavingImage), nil - ) + UIImageWriteToSavedPhotosAlbum(image, self, #selector(didFinishSavingImage), nil) + DispatchQueue.main.async { [weak self] in + self?.saveSucceeded = nil + } } @objc func didFinishSavingImage( - _ image: UIImage, - didFinishSavingWithError error: Error?, - contextInfo: UnsafeRawPointer + _ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer ) { - isSuccess = error == nil if let error = error { SwiftyBeaver.error(error) } + saveSucceeded = error == nil } } diff --git a/EhPanda/App/Tools/Parser.swift b/EhPanda/App/Tools/Parser.swift index 51b30a1e..0151389e 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -579,12 +579,16 @@ struct Parser { return renewedThumbnail } - static func parseGalleryNormalContent(doc: HTMLDocument, index: Int) throws -> (Int, String) { + static func parseGalleryNormalContent(doc: HTMLDocument, index: Int) throws -> (Int, String, String?) { guard let i3Node = doc.at_xpath("//div [@id='i3']"), let imageURL = i3Node.at_css("img")?["src"] else { throw AppError.parseFailed } - return (index, imageURL) + guard let i7Node = doc.at_xpath("//div [@id='i7']"), + let originalImageURL = i7Node.at_xpath("//a")?["href"] + else { return (index, imageURL, nil) } + + return (index, imageURL, originalImageURL) } static func parsePreviewMode(doc: HTMLDocument) throws -> String { diff --git a/EhPanda/App/de.lproj/Localizable.strings b/EhPanda/App/de.lproj/Localizable.strings index 148b7f54..b24d16d7 100644 --- a/EhPanda/App/de.lproj/Localizable.strings +++ b/EhPanda/App/de.lproj/Localizable.strings @@ -113,6 +113,7 @@ //"Reload" = ""; //"Copy" = ""; //"Save" = ""; +//"Save original" = ""; //"Saved to photo library" = ""; // MARK: SettingView diff --git a/EhPanda/App/ja.lproj/Localizable.strings b/EhPanda/App/ja.lproj/Localizable.strings index b82f85c6..e0bd9f20 100644 --- a/EhPanda/App/ja.lproj/Localizable.strings +++ b/EhPanda/App/ja.lproj/Localizable.strings @@ -113,6 +113,7 @@ "Reload" = "再読み込み"; "Copy" = "コピー"; "Save" = "保存"; +"Save original" = "オリジナルを保存"; "Saved to photo library" = "ライブラリに保存しました"; // MARK: SettingView diff --git a/EhPanda/App/ko.lproj/Localizable.strings b/EhPanda/App/ko.lproj/Localizable.strings index b534ce65..fee66dac 100644 --- a/EhPanda/App/ko.lproj/Localizable.strings +++ b/EhPanda/App/ko.lproj/Localizable.strings @@ -113,6 +113,7 @@ "Reload" = "재시도"; "Copy" = "복사"; "Save" = "저장"; +//"Save original" = ""; "Saved to photo library" = "이미지 저장"; // MARK: SettingView diff --git a/EhPanda/App/zh-Hans.lproj/Localizable.strings b/EhPanda/App/zh-Hans.lproj/Localizable.strings index facc81c8..71e8d603 100644 --- a/EhPanda/App/zh-Hans.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hans.lproj/Localizable.strings @@ -113,6 +113,7 @@ "Reload" = "重新加载"; "Copy" = "复制"; "Save" = "保存"; +"Save original" = "保存原图"; "Saved to photo library" = "已保存到图库"; // MARK: SettingView diff --git a/EhPanda/App/zh-Hant.lproj/Localizable.strings b/EhPanda/App/zh-Hant.lproj/Localizable.strings index 2a586954..d3d9738f 100644 --- a/EhPanda/App/zh-Hant.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hant.lproj/Localizable.strings @@ -113,6 +113,7 @@ //"Reload" = ""; //"Copy" = ""; //"Save" = ""; +//"Save original" = ""; //"Saved to photo library" = ""; // MARK: SettingView diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index f7bcc5c2..a40ed7b2 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -87,11 +87,11 @@ enum AppAction { case fetchThumbnails(gid: String, index: Int) case fetchThumbnailsDone(gid: String, index: Int, result: Result<[Int: String], AppError>) case fetchGalleryNormalContents(gid: String, index: Int, thumbnails: [Int: String]) - case fetchGalleryNormalContentsDone(gid: String, index: Int, result: Result<[Int: String], AppError>) + case fetchGalleryNormalContentsDone(gid: String, index: Int, result: Result<([Int: String], [Int: String]), AppError>) case refetchGalleryNormalContent(gid: String, index: Int) case refetchGalleryNormalContentDone(gid: String, index: Int, result: Result<[Int: String], AppError>) case fetchGalleryMPVContent(gid: String, index: Int, isRefetch: Bool = false) - case fetchGalleryMPVContentDone(gid: String, index: Int, result: Result<(String, ReloadToken), AppError>) + case fetchGalleryMPVContentDone(gid: String, index: Int, result: Result<(String, String?, ReloadToken), AppError>) case createEhProfile(name: String) case verifyEhProfile diff --git a/EhPanda/DataFlow/AppCommand.swift b/EhPanda/DataFlow/AppCommand.swift index 97ddb270..5340f62a 100644 --- a/EhPanda/DataFlow/AppCommand.swift +++ b/EhPanda/DataFlow/AppCommand.swift @@ -558,9 +558,11 @@ struct FetchGalleryNormalContentsCommand: AppCommand { store.dispatch(.fetchGalleryNormalContentsDone(gid: gid, index: index, result: .failure(error))) } token.unseal() - } receiveValue: { contents in + } receiveValue: { contents, originalContents in if !contents.isEmpty { - store.dispatch(.fetchGalleryNormalContentsDone(gid: gid, index: index, result: .success(contents))) + store.dispatch(.fetchGalleryNormalContentsDone( + gid: gid, index: index, result: .success((contents, originalContents)) + )) } else { store.dispatch(.fetchGalleryNormalContentsDone( gid: gid, index: index, result: .failure(.networkingFailed)) @@ -617,9 +619,7 @@ struct FetchGalleryMPVContentCommand: AppCommand { func execute(in store: Store) { let token = SubscriptionToken() GalleryMPVContentRequest( - gid: gid, index: index, - mpvKey: mpvKey, imgKey: imgKey, - reloadToken: reloadToken + gid: gid, index: index, mpvKey: mpvKey, imgKey: imgKey, reloadToken: reloadToken ) .publisher .receive(on: DispatchQueue.main) diff --git a/EhPanda/DataFlow/AppState.swift b/EhPanda/DataFlow/AppState.swift index 54779d36..8c0cf06e 100644 --- a/EhPanda/DataFlow/AppState.swift +++ b/EhPanda/DataFlow/AppState.swift @@ -252,13 +252,14 @@ extension AppState { var mpvImageKeys = [String: [Int: String]]() var mpvReloadTokens = [String: [Int: ReloadToken]]() var contents = [String: [Int: String]]() + var originalContents = [String: [Int: String]]() var contentsLoading = [String: [Int: Bool]]() var contentsLoadErrors = [String: [Int: AppError]]() mutating func fulfillContents(gid: String) { - let galleryState = PersistenceController - .fetchGalleryStateNonNil(gid: gid) + let galleryState = PersistenceController.fetchGalleryStateNonNil(gid: gid) contents[gid] = galleryState.contents + originalContents[gid] = galleryState.originalContents thumbnails[gid] = galleryState.thumbnails } @@ -278,8 +279,9 @@ extension AppState { mutating func update(gid: String, thumbnails: [Int: String]) { update(gid: gid, stored: &self.thumbnails, new: thumbnails) } - mutating func update(gid: String, contents: [Int: String]) { + mutating func update(gid: String, contents: [Int: String], originalContents: [Int: String]) { update(gid: gid, stored: &self.contents, new: contents) + update(gid: gid, stored: &self.originalContents, new: originalContents) } } } diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index 6419c8d1..454afc1b 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -60,11 +60,12 @@ final class Store: ObservableObject { + "thumbnails: \(thumbnails.count))" ) case .fetchGalleryNormalContentsDone(let gid, let index, let result): - if case .success(let contents) = result { + if case .success(let (contents, originalContents)) = result { SwiftyBeaver.verbose( "[ACTION]: fetchGalleryNormalContentsDone(" + "gid: \(gid), index: \(index), " - + "contents: \(contents.count))" + + "contents: \(contents.count), " + + "originalContents: \(originalContents.count))" ) } case .fetchMPVKeysDone(let gid, let index, let result): @@ -686,9 +687,9 @@ final class Store: ObservableObject { batchRange.forEach { appState.contentInfo.contentsLoading[gid]?[$0] = false } switch result { - case .success(let contents): - appState.contentInfo.update(gid: gid, contents: contents) - PersistenceController.update(gid: gid, contents: contents) + case .success(let (contents, originalContents)): + appState.contentInfo.update(gid: gid, contents: contents, originalContents: originalContents) + PersistenceController.update(gid: gid, contents: contents, originalContents: originalContents) case .failure(let error): batchRange.forEach { appState.contentInfo.contentsLoadErrors[gid]?[$0] = error } } @@ -714,8 +715,8 @@ final class Store: ObservableObject { switch result { case .success(let content): - appState.contentInfo.update(gid: gid, contents: content) - PersistenceController.update(gid: gid, contents: content) + appState.contentInfo.update(gid: gid, contents: content, originalContents: [:]) + PersistenceController.update(gid: gid, contents: content, originalContents: [:]) case .failure(let error): appState.contentInfo.contentsLoadErrors[gid]?[index] = error } @@ -738,9 +739,13 @@ final class Store: ObservableObject { case .fetchGalleryMPVContentDone(let gid, let index, let result): appState.contentInfo.contentsLoading[gid]?[index] = false - if case .success(let (imageURL, reloadToken)) = result { - appState.contentInfo.update(gid: gid, contents: [index: imageURL]) - PersistenceController.update(gid: gid, contents: [index: imageURL]) + if case .success(let (imageURL, originalImageURL, reloadToken)) = result { + var originalContents = [Int: String]() + if let originalImageURL = originalImageURL { + originalContents[index] = originalImageURL + } + appState.contentInfo.update(gid: gid, contents: [index: imageURL], originalContents: originalContents) + PersistenceController.update(gid: gid, contents: [index: imageURL], originalContents: originalContents) if appState.contentInfo.mpvReloadTokens[gid] == nil { appState.contentInfo.mpvReloadTokens[gid] = [index: reloadToken] } else { diff --git a/EhPanda/Database/PersistenceAccessor.swift b/EhPanda/Database/PersistenceAccessor.swift index 142d5aa0..ab9c042d 100644 --- a/EhPanda/Database/PersistenceAccessor.swift +++ b/EhPanda/Database/PersistenceAccessor.swift @@ -193,16 +193,25 @@ extension PersistenceController { } } } - static func update(gid: String, contents: [Int: String]) { + static func update(gid: String, contents: [Int: String], originalContents: [Int: String]) { update(gid: gid) { galleryStateMO in 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() - } + update(gid: gid, storedData: &galleryStateMO.contents, new: contents) + guard !originalContents.isEmpty else { return } + update(gid: gid, storedData: &galleryStateMO.originalContents, new: originalContents) + } + } + private static func update( + gid: String, storedData: inout Data?, new: [Int: T] + ) { + guard !new.isEmpty else { return } + + if let storedDictionary = storedData?.toObject() as [Int: T]? { + storedData = storedDictionary.merging( + new, uniquingKeysWith: { _, new in new } + ).toData() + } else { + storedData = new.toData() } } static func update(fetchedState: GalleryState) { diff --git a/EhPanda/Network/Request.swift b/EhPanda/Network/Request.swift index cb582c3a..425c1d6a 100644 --- a/EhPanda/Network/Request.swift +++ b/EhPanda/Network/Request.swift @@ -512,7 +512,7 @@ struct ThumbnailsRequest { struct GalleryNormalContentsRequest { let thumbnails: [Int: String] - var publisher: AnyPublisher<[Int: String], AppError> { + var publisher: AnyPublisher<([Int: String], [Int: String]), AppError> { thumbnails.publisher .flatMap { index, url in URLSession.shared.dataTaskPublisher(for: url.safeURL()).genericRetry() @@ -521,10 +521,12 @@ struct GalleryNormalContentsRequest { } .collect().map { tuples in var contents = [Int: String]() - for (index, imageURL) in tuples { + var originalContents = [Int: String]() + for (index, imageURL, originalImageURL) in tuples { contents[index] = imageURL + originalContents[index] = originalImageURL } - return contents + return (contents, originalContents) } .mapError(mapAppError).eraseToAnyPublisher() } @@ -599,7 +601,7 @@ struct GalleryMPVContentRequest { let imgKey: String let reloadToken: ReloadToken? - var publisher: AnyPublisher<(String, ReloadToken), AppError> { + var publisher: AnyPublisher<(String, String?, ReloadToken), AppError> { let url = Defaults.URL.ehAPI() var params: [String: Any] = [ "method": "imagedispatch", "gid": gid, @@ -625,7 +627,12 @@ struct GalleryMPVContentRequest { let imageURL = dict["i"] as? String, let reloadToken = dict["s"] else { throw AppError.parseFailed } - return (imageURL, reloadToken) + + if let originalImageURL = dict["lf"] as? String { + return (imageURL, Defaults.URL.host + originalImageURL, reloadToken) + } else { + return (imageURL, nil, reloadToken) + } } .mapError(mapAppError).eraseToAnyPublisher() } diff --git a/EhPanda/View/Reading/ReadingView.swift b/EhPanda/View/Reading/ReadingView.swift index 1d762d37..9e0612b7 100644 --- a/EhPanda/View/Reading/ReadingView.swift +++ b/EhPanda/View/Reading/ReadingView.swift @@ -39,8 +39,7 @@ struct ReadingView: View, StoreAccessor, PersistenceAccessor { @State private var pageCount = 1 - @State private var imageSaver: ImageSaver? - @State private var isImageSaveSuccess: Bool? + @StateObject private var imageSaver = ImageSaver() @State private var hudVisible = false @State private var hudConfig = TTProgressHUDConfig() @@ -94,7 +93,7 @@ struct ReadingView: View, StoreAccessor, PersistenceAccessor { .onChange(of: setting.exceptCover, perform: tryUpdatePagerIndex) .onChange(of: setting.readingDirection, perform: tryUpdatePagerIndex) .onChange(of: setting.enablesDualPageMode, perform: tryUpdatePagerIndex) - .onChange(of: isImageSaveSuccess, perform: { newValue in + .onChange(of: imageSaver.saveSucceeded, perform: { newValue in guard let isSuccess = newValue else { return } presentHUD(isSuccess: isSuccess, caption: "Saved to photo library") }) @@ -191,13 +190,18 @@ struct ReadingView: View, StoreAccessor, PersistenceAccessor { Label("Reload", systemImage: "arrow.counterclockwise") }) if let imageURL = galleryContents[index], !imageURL.isEmpty { - Button(action: { copyImage(url: imageURL) }, label: { + Button(action: { Task { await copyImage(url: imageURL) } }, label: { Label("Copy", systemImage: "plus.square.on.square") }) - Button(action: { saveImage(url: imageURL) }, label: { + Button(action: { Task { await saveImage(url: imageURL) } }, label: { Label("Save", systemImage: "square.and.arrow.down") }) - Button(action: { shareImage(url: imageURL) }, label: { + if let originalImageURL = galleryOriginalContents[index], !originalImageURL.isEmpty { + Button(action: { Task { await saveImage(url: originalImageURL) } }, label: { + Label("Save original", systemImage: "square.and.arrow.down.on.square") + }) + } + Button(action: { Task { await shareImage(url: imageURL) } }, label: { Label("Share", systemImage: "square.and.arrow.up") }) } @@ -256,6 +260,9 @@ private extension ReadingView { var galleryContents: [Int: String] { contentInfo.contents[gid] ?? [:] } + var galleryOriginalContents: [Int: String] { + contentInfo.originalContents[gid] ?? [:] + } var galleryLoadingFlags: [Int: Bool] { contentInfo.contentsLoading[gid] ?? [:] } @@ -428,37 +435,27 @@ private extension ReadingView { } // MARK: ContextMenu - func retrieveImage(url: String, completion: @escaping (UIImage) -> Void) { - KingfisherManager.shared.cache.retrieveImage(forKey: url) { result in - switch result { - case .success(let result): - if let image = result.image { - completion(image) - } else { - presentHUD(isSuccess: false) - } - case .failure(let error): - SwiftyBeaver.error(error) - presentHUD(isSuccess: false) - } - } - } - func copyImage(url: String) { - retrieveImage(url: url) { image in - UIPasteboard.general.image = image - presentHUD(isSuccess: true, caption: "Copied to clipboard") + func copyImage(url: String) async { + guard let image = try? await imageSaver.retrieveImage(url: url.safeURL()) else { + presentHUD(isSuccess: false) + return } + UIPasteboard.general.image = image + presentHUD(isSuccess: true, caption: "Copied to clipboard") } - func saveImage(url: String) { - retrieveImage(url: url) { image in - imageSaver = ImageSaver(isSuccess: $isImageSaveSuccess) - imageSaver?.saveImage(image) + func saveImage(url: String) async { + guard let image = try? await imageSaver.retrieveImage(url: url.safeURL()) else { + presentHUD(isSuccess: false) + return } + imageSaver.saveImage(image) } - func shareImage(url: String) { - retrieveImage(url: url) { image in - AppUtil.presentActivity(items: [image]) + func shareImage(url: String) async { + guard let image = try? await imageSaver.retrieveImage(url: url.safeURL()) else { + presentHUD(isSuccess: false) + return } + AppUtil.presentActivity(items: [image]) } func presentHUD(isSuccess: Bool, caption: String? = nil) { let type: TTProgressHUDType = isSuccess ? .success : .error @@ -480,7 +477,6 @@ private extension ReadingView { ) hudVisible = true } - isImageSaveSuccess = nil } // MARK: Gesture From 03dfc6c8e6c27df8ff5d56597935d5740c0e04ea 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: Sun, 28 Nov 2021 17:03:43 +0900 Subject: [PATCH 11/12] refactor: Use alias instead of `SwiftyBeaver` --- EhPanda/App/EhPandaApp.swift | 4 ++-- EhPanda/App/Extensions.swift | 5 ++--- EhPanda/App/Tools/AppEnvStorage.swift | 4 +--- EhPanda/App/Tools/ImageSaver.swift | 7 +++---- EhPanda/App/Tools/Parser.swift | 5 +---- EhPanda/DataFlow/Store.swift | 19 +++++++++---------- EhPanda/Database/Persistence.swift | 5 ++--- EhPanda/Models/Misc.swift | 2 ++ EhPanda/Network/DFExtensions.swift | 7 +++---- EhPanda/Network/DFRequest.swift | 3 +-- EhPanda/Network/DFStreamHandler.swift | 17 ++++++++--------- EhPanda/Network/DFURLProtocol.swift | 5 ++--- EhPanda/Network/DomainResolver.swift | 2 -- EhPanda/Network/Request.swift | 3 +-- EhPanda/View/Detail/ArchiveView.swift | 11 +++++------ EhPanda/View/Detail/AssociatedView.swift | 17 ++++++++--------- EhPanda/View/Detail/TorrentsView.swift | 7 +++---- EhPanda/View/Reading/ReadingView.swift | 1 - .../View/Setting/AppearanceSettingView.swift | 3 +-- EhPanda/View/Setting/EhSettingView.swift | 19 +++++++++---------- EhPanda/View/Setting/GeneralSettingView.swift | 3 +-- EhPanda/View/Setting/SettingView.swift | 1 - EhPanda/View/Setting/WebView.swift | 3 +-- 23 files changed, 65 insertions(+), 88 deletions(-) diff --git a/EhPanda/App/EhPandaApp.swift b/EhPanda/App/EhPandaApp.swift index cfe93161..32ed00e6 100644 --- a/EhPanda/App/EhPandaApp.swift +++ b/EhPanda/App/EhPandaApp.swift @@ -98,10 +98,10 @@ private extension EhPandaApp { configure(file: &file) configure(console: &console) - SwiftyBeaver.addDestination(file) + Logger.addDestination(file) #if DEBUG guard !AppUtil.isUnitTesting else { return } - SwiftyBeaver.addDestination(console) + Logger.addDestination(console) #endif } func configure(file: inout FileDestination) { diff --git a/EhPanda/App/Extensions.swift b/EhPanda/App/Extensions.swift index 2345e33a..6a415da4 100644 --- a/EhPanda/App/Extensions.swift +++ b/EhPanda/App/Extensions.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftyBeaver // MARK: UINavigationController extension UINavigationController: UIGestureRecognizerDelegate { @@ -106,7 +105,7 @@ extension String { if isValidURL { return URL(string: self).forceUnwrapped } else { - SwiftyBeaver.error("Invalid URL, redirect to default host...") + Logger.error("Invalid URL, redirect to default host...") return URL(string: Defaults.URL.ehentai).forceUnwrapped } } @@ -172,7 +171,7 @@ extension Optional { if let value = self { return value } - SwiftyBeaver.error( + Logger.error( "Failed in force unwrapping...", context: ["type": Wrapped.self] ) diff --git a/EhPanda/App/Tools/AppEnvStorage.swift b/EhPanda/App/Tools/AppEnvStorage.swift index ffed37e5..006f33c0 100644 --- a/EhPanda/App/Tools/AppEnvStorage.swift +++ b/EhPanda/App/Tools/AppEnvStorage.swift @@ -5,8 +5,6 @@ // Created by 荒木辰造 on R 3/07/10. // -import SwiftyBeaver - @propertyWrapper struct AppEnvStorage { private var key: String @@ -23,7 +21,7 @@ struct AppEnvStorage { return value } } - SwiftyBeaver.error("Failed in force downcasting to generic type...") + Logger.error("Failed in force downcasting to generic type...") return nil } diff --git a/EhPanda/App/Tools/ImageSaver.swift b/EhPanda/App/Tools/ImageSaver.swift index 7e4ed10c..393d394e 100644 --- a/EhPanda/App/Tools/ImageSaver.swift +++ b/EhPanda/App/Tools/ImageSaver.swift @@ -7,7 +7,6 @@ import SwiftUI import Kingfisher -import SwiftyBeaver final class ImageSaver: NSObject, ObservableObject { @Published var saveSucceeded: Bool? @@ -34,7 +33,7 @@ final class ImageSaver: NSObject, ObservableObject { continuation.resume(throwing: AppError.notFound) } case .failure(let error): - SwiftyBeaver.error(error) + Logger.error(error) continuation.resume(throwing: error) } } @@ -47,7 +46,7 @@ final class ImageSaver: NSObject, ObservableObject { case .success(let result): continuation.resume(returning: result.image) case .failure(let error): - SwiftyBeaver.error(error) + Logger.error(error) continuation.resume(throwing: error) } } @@ -64,7 +63,7 @@ final class ImageSaver: NSObject, ObservableObject { _ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer ) { if let error = error { - SwiftyBeaver.error(error) + Logger.error(error) } saveSucceeded = error == nil } diff --git a/EhPanda/App/Tools/Parser.swift b/EhPanda/App/Tools/Parser.swift index 0151389e..18e6e357 100644 --- a/EhPanda/App/Tools/Parser.swift +++ b/EhPanda/App/Tools/Parser.swift @@ -7,9 +7,6 @@ import Kanna import UIKit -import Foundation -import SwiftUI -import SwiftyBeaver struct Parser { // MARK: List @@ -1602,7 +1599,7 @@ extension Parser { return .minutes(minutes, seconds: nil) } } else { - SwiftyBeaver.error( + Logger.error( "Unrecognized BanInterval format", context: [ "expireDescription": expireDescription ] diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index 454afc1b..c002b31d 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -7,7 +7,6 @@ import SwiftUI import Combine -import SwiftyBeaver final class Store: ObservableObject { @Published var appState = AppState() @@ -34,12 +33,12 @@ final class Store: ObservableObject { private func privateDispatch(_ action: AppAction) { let description = String(describing: action) if description.contains("error") { - SwiftyBeaver.error("[ACTION]: " + description) + Logger.error("[ACTION]: " + description) } else { switch action { case .fetchGalleryPreviewsDone(let gid, let pageNumber, let result): if case .success(let previews) = result { - SwiftyBeaver.verbose( + Logger.verbose( "[ACTION]: fetchGalleryPreviewsDone(" + "gid: \(gid), pageNumber: \(pageNumber), " + "previews: \(previews.count))" @@ -47,21 +46,21 @@ final class Store: ObservableObject { } case .fetchThumbnailsDone(let gid, let index, let result): if case .success(let contents) = result { - SwiftyBeaver.verbose( + Logger.verbose( "[ACTION]: fetchThumbnailsDone(" + "gid: \(gid), index: \(index), " + "contents: \(contents.count))" ) } case .fetchGalleryNormalContents(let gid, let index, let thumbnails): - SwiftyBeaver.verbose( + Logger.verbose( "[ACTION]: fetchGalleryNormalContents(" + "gid: \(gid), index: \(index), " + "thumbnails: \(thumbnails.count))" ) case .fetchGalleryNormalContentsDone(let gid, let index, let result): if case .success(let (contents, originalContents)) = result { - SwiftyBeaver.verbose( + Logger.verbose( "[ACTION]: fetchGalleryNormalContentsDone(" + "gid: \(gid), index: \(index), " + "contents: \(contents.count), " @@ -70,21 +69,21 @@ final class Store: ObservableObject { } case .fetchMPVKeysDone(let gid, let index, let result): if case .success(let (mpvKey, imgKeys)) = result { - SwiftyBeaver.verbose( + Logger.verbose( "[ACTION]: fetchMPVKeysDone(" + "gid: \(gid), index: \(index), " + "mpvKey: \(mpvKey), imgKeys: \(imgKeys.count))" ) } default: - SwiftyBeaver.verbose("[ACTION]: " + description) + Logger.verbose("[ACTION]: " + description) } } let (state, command) = reduce(state: appState, action: action) appState = state guard let command = command else { return } - SwiftyBeaver.verbose("[COMMAND]: \(command)") + Logger.verbose("[COMMAND]: \(command)") command.execute(in: self) } @@ -772,7 +771,7 @@ final class Store: ObservableObject { } else if profileNotFound { dispatch(.createEhProfile(name: "EhPanda")) } else { - SwiftyBeaver.error("Found profile but failed in parsing value.") + Logger.error("Found profile but failed in parsing value.") } } case .favorGallery(let gid, let favIndex): diff --git a/EhPanda/Database/Persistence.swift b/EhPanda/Database/Persistence.swift index b7cf7260..7842ef8d 100644 --- a/EhPanda/Database/Persistence.swift +++ b/EhPanda/Database/Persistence.swift @@ -6,7 +6,6 @@ // import CoreData -import SwiftyBeaver struct PersistenceController { static let shared = PersistenceController() @@ -16,7 +15,7 @@ struct PersistenceController { container.loadPersistentStores { guard let error = $1 else { return } - SwiftyBeaver.error(error as Any) + Logger.error(error as Any) } return container }() @@ -33,7 +32,7 @@ struct PersistenceController { do { try context.save() } catch { - SwiftyBeaver.error(error) + Logger.error(error) fatalError("Unresolved error \(error)") } } diff --git a/EhPanda/Models/Misc.swift b/EhPanda/Models/Misc.swift index 673339f9..04a96bc7 100644 --- a/EhPanda/Models/Misc.swift +++ b/EhPanda/Models/Misc.swift @@ -6,6 +6,7 @@ // import Foundation +import SwiftyBeaver typealias Percentage = Int typealias Keyword = String @@ -14,6 +15,7 @@ typealias APIKey = String typealias CurrentGP = String typealias CurrentCredits = String typealias ReloadToken = Any +typealias Logger = SwiftyBeaver typealias FavoritesSortOrder = EhSettingFavoritesSortOrder struct PageNumber: Equatable { diff --git a/EhPanda/Network/DFExtensions.swift b/EhPanda/Network/DFExtensions.swift index 56823711..9ee6cc53 100644 --- a/EhPanda/Network/DFExtensions.swift +++ b/EhPanda/Network/DFExtensions.swift @@ -6,7 +6,6 @@ // import Foundation -import SwiftyBeaver import DeprecatedAPI // MARK: Global @@ -14,7 +13,7 @@ private func forceDowncast(object: Any) -> T! { if let downcastedValue = object as? T { return downcastedValue } - SwiftyBeaver.error( + Logger.error( "Failed in force downcasting...", context: [ "type": T.self @@ -150,10 +149,10 @@ extension URLRequest { if readSize > 0 { body.append(buffer, count: readSize) } else if readSize == 0 { - SwiftyBeaver.verbose("HTTPBodyStream read EOF.") + Logger.verbose("HTTPBodyStream read EOF.") } else { if let error = stream.streamError as Error? { - SwiftyBeaver.error("HTTPBodyStream read Error: \(error).") + Logger.error("HTTPBodyStream read Error: \(error).") } } } while readSize > 0 diff --git a/EhPanda/Network/DFRequest.swift b/EhPanda/Network/DFRequest.swift index 13c31247..4e8e2442 100644 --- a/EhPanda/Network/DFRequest.swift +++ b/EhPanda/Network/DFRequest.swift @@ -6,7 +6,6 @@ // import Foundation -import SwiftyBeaver struct DFRequest { var request: URLRequest @@ -46,7 +45,7 @@ struct DFRequest { mutating func resume() { if !request.urlContainsImageURL { - SwiftyBeaver.verbose("Request from: \(request.url?.absoluteString ?? "")") + Logger.verbose("Request from: \(request.url?.absoluteString ?? "")") } stream.schedule(in: RunLoop.current, forMode: .common) diff --git a/EhPanda/Network/DFStreamHandler.swift b/EhPanda/Network/DFStreamHandler.swift index 769fd813..ee0cd802 100644 --- a/EhPanda/Network/DFStreamHandler.swift +++ b/EhPanda/Network/DFStreamHandler.swift @@ -6,7 +6,6 @@ // import Foundation -import SwiftyBeaver class DFStreamEventHandler: NSObject { private var request: DFRequest @@ -84,7 +83,7 @@ private extension DFStreamEventHandler { if SecTrustEvaluateWithError(serverTrust, &error) { return true } else { - SwiftyBeaver.error(error as Any) + Logger.error(error as Any) return false } } @@ -94,7 +93,7 @@ private extension DFStreamEventHandler { extension DFStreamEventHandler: StreamDelegate { func stream(_ aStream: Stream, handle eventCode: Stream.Event) { guard let input = aStream as? InputStream else { - SwiftyBeaver.error("Unexpected stream, should be a InputStream, but \(aStream).") + Logger.error("Unexpected stream, should be a InputStream, but \(aStream).") return } @@ -119,14 +118,14 @@ private extension DFStreamEventHandler { func openCompleted() { if !request.request.urlContainsImageURL { let urlString = request.request.url?.absoluteString ?? "" - SwiftyBeaver.verbose("Stream open completed for: \(urlString).") + Logger.verbose("Stream open completed for: \(urlString).") } } func endEncountered(_ stream: InputStream) { if !request.request.urlContainsImageURL { let urlString = request.request.url?.absoluteString ?? "" - SwiftyBeaver.verbose("Stream end off for: \(urlString).") + Logger.verbose("Stream end off for: \(urlString).") } let message = stream.httpMessage() @@ -137,7 +136,7 @@ private extension DFStreamEventHandler { } else { if !self.request.request.urlContainsImageURL { let urlString = self.request.request.url?.absoluteString ?? "" - SwiftyBeaver.verbose("Request loading finished for: \(urlString).") + Logger.verbose("Request loading finished for: \(urlString).") } self.request.delegate?.dfRequestDidFinishLoading(self.request) } @@ -168,7 +167,7 @@ private extension DFStreamEventHandler { url = originalURL.appendingPathComponent(url.absoluteString) } - SwiftyBeaver.warning("Request redirected to: \(url.absoluteString).") + Logger.warning("Request redirected to: \(url.absoluteString).") var req = URLRequest(url: url) req.httpMethod = "GET" @@ -187,7 +186,7 @@ private extension DFStreamEventHandler { if let err = stream.streamError as NSError? { if !request.request.urlContainsImageURL { let urlString = request.request.url?.absoluteString ?? "" - SwiftyBeaver.error("\(stream) Occurred error: \(err) for: \(urlString).") + Logger.error("\(stream) Occurred error: \(err) for: \(urlString).") } request.delegate?.dfRequest( request.request, @@ -198,6 +197,6 @@ private extension DFStreamEventHandler { } func defaultHandle(event: Stream.Event) { - SwiftyBeaver.error("An unexpected Evnet: \(event) occurred.") + Logger.error("An unexpected Evnet: \(event) occurred.") } } diff --git a/EhPanda/Network/DFURLProtocol.swift b/EhPanda/Network/DFURLProtocol.swift index b6b7b633..c31db3af 100644 --- a/EhPanda/Network/DFURLProtocol.swift +++ b/EhPanda/Network/DFURLProtocol.swift @@ -6,7 +6,6 @@ // import Foundation -import SwiftyBeaver class DFURLProtocol: URLProtocol { private var dfRequest: DFRequest? @@ -16,12 +15,12 @@ class DFURLProtocol: URLProtocol { for request: URLRequest) -> URLRequest { request } override class func canInit(with request: URLRequest) -> Bool { if property(forKey: requestIdentifier, in: request) != nil { - SwiftyBeaver.error("URLRequest has been initialized.") + Logger.error("URLRequest has been initialized.") return false } if !["http", "https"].contains(request.url?.scheme) { let scheme = request.url?.scheme ?? "nil" - SwiftyBeaver.error("URL scheme \"\(scheme)\" is not supported.") + Logger.error("URL scheme \"\(scheme)\" is not supported.") return false } return true diff --git a/EhPanda/Network/DomainResolver.swift b/EhPanda/Network/DomainResolver.swift index 5c6fb1b2..cbedc241 100644 --- a/EhPanda/Network/DomainResolver.swift +++ b/EhPanda/Network/DomainResolver.swift @@ -5,8 +5,6 @@ // Created by 荒木辰造 on R 3/07/13. // -import SwiftyBeaver - struct DomainResolver { static func resolve(domain: String) -> String? { ResolvableDomain(rawValue: domain)?.ipPool.randomElement() diff --git a/EhPanda/Network/Request.swift b/EhPanda/Network/Request.swift index 425c1d6a..f7c104b9 100644 --- a/EhPanda/Network/Request.swift +++ b/EhPanda/Network/Request.swift @@ -9,10 +9,9 @@ import Kanna import OpenCC import Combine import Foundation -import SwiftyBeaver private func mapAppError(error: Error) -> AppError { - SwiftyBeaver.error(error) + Logger.error(error) switch error { case is ParseError: diff --git a/EhPanda/View/Detail/ArchiveView.swift b/EhPanda/View/Detail/ArchiveView.swift index a0a16155..9c71c1e8 100644 --- a/EhPanda/View/Detail/ArchiveView.swift +++ b/EhPanda/View/Detail/ArchiveView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftyBeaver import TTProgressHUD struct ArchiveView: View, StoreAccessor, PersistenceAccessor { @@ -147,7 +146,7 @@ private extension ArchiveView { if case .failure(let error) = completion { loadError = error - SwiftyBeaver.error( + Logger.error( "GalleryArchiveRequest failed", context: ["ArchiveURL": archiveURL, "Error": error] ) @@ -160,7 +159,7 @@ private extension ArchiveView { store.dispatch(.fetchGalleryArchiveFundsDone( result: .success((galleryPoints, credits))) ) - SwiftyBeaver.info( + Logger.info( "GalleryArchiveRequest succeeded", context: [ "ArchiveURL": archiveURL, "Archive": archive as Any, @@ -190,7 +189,7 @@ private extension ArchiveView { if case .failure(let error) = completion { sendFailedFlag = true - SwiftyBeaver.error( + Logger.error( "SendDownloadCommandRequest failed", context: [ "ArchiveURL": archiveURL, @@ -209,7 +208,7 @@ private extension ArchiveView { Defaults.Response.invalidResolution, .none: sendFailedFlag = true - SwiftyBeaver.error( + Logger.error( "SendDownloadCommandRequest failed", context: [ "ArchiveURL": archiveURL, @@ -218,7 +217,7 @@ private extension ArchiveView { ] ) default: - SwiftyBeaver.info( + Logger.info( "SendDownloadCommandRequest succeeded", context: [ "ArchiveURL": archiveURL, diff --git a/EhPanda/View/Detail/AssociatedView.swift b/EhPanda/View/Detail/AssociatedView.swift index ba54b301..8b4fb076 100644 --- a/EhPanda/View/Detail/AssociatedView.swift +++ b/EhPanda/View/Detail/AssociatedView.swift @@ -7,7 +7,6 @@ import SwiftUI import AlertKit -import SwiftyBeaver struct AssociatedView: View, StoreAccessor { @EnvironmentObject var store: Store @@ -136,10 +135,10 @@ private extension AssociatedView { .sink { completion in loadingFlag = false if case .failure(let error) = completion { - SwiftyBeaver.error(error) + Logger.error(error) loadError = error - SwiftyBeaver.error( + Logger.error( "SearchItemsRequest failed", context: [ "Keyword": keyword.isEmpty ? title : keyword, @@ -153,7 +152,7 @@ private extension AssociatedView { if !galleries.isEmpty { associatedItems = galleries - SwiftyBeaver.info( + Logger.info( "SearchItemsRequest succeeded", context: [ "Keyword": keyword.isEmpty ? title : keyword, "PageNumber": pageNumber, @@ -163,7 +162,7 @@ private extension AssociatedView { } else { loadError = .notFound - SwiftyBeaver.error( + Logger.error( "SearchItemsRequest failed", context: [ "Keyword": keyword.isEmpty ? title : keyword, @@ -196,9 +195,9 @@ private extension AssociatedView { moreLoadingFlag = false if case .failure(let error) = completion { moreLoadFailedFlag = true - SwiftyBeaver.error(error) + Logger.error(error) - SwiftyBeaver.error( + Logger.error( "MoreSearchItemsRequest failed", context: [ "Keyword": keyword, "LastID": lastID, @@ -221,7 +220,7 @@ private extension AssociatedView { } PersistenceController.add(galleries: galleries) - SwiftyBeaver.info( + Logger.info( "MoreSearchItemsRequest succeeded", context: [ "Keyword": keyword, "LastID": lastID, "PageNumber": pageNumber, @@ -231,7 +230,7 @@ private extension AssociatedView { if galleries.isEmpty && pageNumber.current < pageNumber.maximum { fetchMoreAssociatedItems() - SwiftyBeaver.warning("MoreSearchItemsRequest result empty, requesting more...") + Logger.warning("MoreSearchItemsRequest result empty, requesting more...") } } .seal(in: token) diff --git a/EhPanda/View/Detail/TorrentsView.swift b/EhPanda/View/Detail/TorrentsView.swift index 44d16953..2ae12e4a 100644 --- a/EhPanda/View/Detail/TorrentsView.swift +++ b/EhPanda/View/Detail/TorrentsView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftyBeaver import TTProgressHUD struct TorrentsView: View, StoreAccessor { @@ -101,10 +100,10 @@ private extension TorrentsView { .publisher.receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { - SwiftyBeaver.error(error) + Logger.error(error) loadError = error - SwiftyBeaver.error( + Logger.error( "GalleryTorrentsRequest Failed", context: ["gid": gid, "Token": token, "Error": error] ) @@ -114,7 +113,7 @@ private extension TorrentsView { } receiveValue: { torrents = $0 - SwiftyBeaver.info( + Logger.info( "GalleryTorrentsRequest succeeded", context: ["gid": gid, "Token": token, "Torrents count": $0.count] ) diff --git a/EhPanda/View/Reading/ReadingView.swift b/EhPanda/View/Reading/ReadingView.swift index 9e0612b7..61bf3513 100644 --- a/EhPanda/View/Reading/ReadingView.swift +++ b/EhPanda/View/Reading/ReadingView.swift @@ -9,7 +9,6 @@ import SwiftUI import Combine import Kingfisher import SwiftUIPager -import SwiftyBeaver import TTProgressHUD struct ReadingView: View, StoreAccessor, PersistenceAccessor { diff --git a/EhPanda/View/Setting/AppearanceSettingView.swift b/EhPanda/View/Setting/AppearanceSettingView.swift index 852e6175..afc8326a 100644 --- a/EhPanda/View/Setting/AppearanceSettingView.swift +++ b/EhPanda/View/Setting/AppearanceSettingView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftyBeaver struct AppearanceSettingView: View, StoreAccessor { @EnvironmentObject var store: Store @@ -132,7 +131,7 @@ private struct SelectAppIconView: View { UIApplication.shared.setAlternateIconName(icon.fileName) { error in if let error = error { HapticUtil.generateNotificationFeedback(style: .error) - SwiftyBeaver.error(error) + Logger.error(error) } selectAction() } diff --git a/EhPanda/View/Setting/EhSettingView.swift b/EhPanda/View/Setting/EhSettingView.swift index 841da472..c0a9df24 100644 --- a/EhPanda/View/Setting/EhSettingView.swift +++ b/EhPanda/View/Setting/EhSettingView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftyBeaver struct EhSettingView: View, StoreAccessor { @EnvironmentObject var store: Store @@ -126,10 +125,10 @@ private extension EhSettingView { .sink { completion in loadingFlag = false if case .failure(let error) = completion { - SwiftyBeaver.error(error) + Logger.error(error) loadError = error - SwiftyBeaver.error( + Logger.error( "EhSettingRequest failed", context: [ "Error": error ] ) @@ -138,7 +137,7 @@ private extension EhSettingView { } receiveValue: { ehSetting in self.ehSetting = ehSetting - SwiftyBeaver.info( + Logger.info( "EhSettingRequest succeeded", context: [ "EhProfiles": ehSetting.ehProfiles ] ) @@ -156,10 +155,10 @@ private extension EhSettingView { .sink { completion in submittingFlag = false if case .failure(let error) = completion { - SwiftyBeaver.error(error) + Logger.error(error) loadError = error - SwiftyBeaver.error( + Logger.error( "SubmitEhSettingChangesRequest failed", context: [ "Error": error ] ) @@ -168,7 +167,7 @@ private extension EhSettingView { } receiveValue: { ehSetting in self.ehSetting = ehSetting - SwiftyBeaver.info( + Logger.info( "SubmitEhSettingChangesRequest succeeded", context: [ "EhProfiles": ehSetting.ehProfiles ] ) @@ -185,10 +184,10 @@ private extension EhSettingView { .sink { completion in submittingFlag = false if case .failure(let error) = completion { - SwiftyBeaver.error(error) + Logger.error(error) loadError = error - SwiftyBeaver.error( + Logger.error( "EhProfileRequest failed", context: [ "Action": action as Any, "Name": name as Any, @@ -200,7 +199,7 @@ private extension EhSettingView { } receiveValue: { ehSetting in self.ehSetting = ehSetting - SwiftyBeaver.info( + Logger.info( "EhProfileRequest succeeded", context: [ "Action": action as Any, "Name": name as Any, diff --git a/EhPanda/View/Setting/GeneralSettingView.swift b/EhPanda/View/Setting/GeneralSettingView.swift index 57b8d33b..1cec013e 100644 --- a/EhPanda/View/Setting/GeneralSettingView.swift +++ b/EhPanda/View/Setting/GeneralSettingView.swift @@ -7,7 +7,6 @@ import SwiftUI import Kingfisher -import SwiftyBeaver import LocalAuthentication struct GeneralSettingView: View, StoreAccessor { @@ -124,7 +123,7 @@ private extension GeneralSettingView { case .success(let size): diskImageCacheSize = readableUnit(bytes: size) case .failure(let error): - SwiftyBeaver.error(error) + Logger.error(error) } } } diff --git a/EhPanda/View/Setting/SettingView.swift b/EhPanda/View/Setting/SettingView.swift index ba2517b6..1bf9ae64 100644 --- a/EhPanda/View/Setting/SettingView.swift +++ b/EhPanda/View/Setting/SettingView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import SwiftyBeaver struct SettingView: View, StoreAccessor { @EnvironmentObject var store: Store diff --git a/EhPanda/View/Setting/WebView.swift b/EhPanda/View/Setting/WebView.swift index 07432fbc..808c3783 100644 --- a/EhPanda/View/Setting/WebView.swift +++ b/EhPanda/View/Setting/WebView.swift @@ -7,7 +7,6 @@ import WebKit import SwiftUI -import SwiftyBeaver struct WebView: UIViewControllerRepresentable { static let loginURLString = "https://forums.e-hentai.org/index.php?act=Login" @@ -48,7 +47,7 @@ struct WebView: UIViewControllerRepresentable { } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - SwiftyBeaver.error(error) + Logger.error(error) } } From 96189a5d2f2324cfcab0f28b49fa32d7e2e0c2c4 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: Sun, 28 Nov 2021 17:07:01 +0900 Subject: [PATCH 12/12] docs: Update `deploy.yml` --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 768ee159..a81bfd84 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.4.0' + APP_VERSION: '1.5.0' SCHEME_NAME: 'EhPanda' ALTSTORE_JSON_PATH: './AltStore.json' BUILDS_PATH: '/tmp/action-builds'