From 489dbbd2fd2ed284671a3cb892efe26ce5e5a74b 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, 25 Sep 2021 11:08:39 +0800 Subject: [PATCH 1/8] Modified HomeView UI slightly --- EhPanda/App/Utility.swift | 10 ++- EhPanda/View/Home/FilterView.swift | 88 ++++++++++--------- EhPanda/View/Home/Home.swift | 22 ++--- EhPanda/View/Home/HomeView.swift | 57 ++++++++---- .../View/Setting/AppearanceSettingView.swift | 18 ---- EhPanda/View/Setting/GeneralSettingView.swift | 20 ++++- 6 files changed, 119 insertions(+), 96 deletions(-) diff --git a/EhPanda/App/Utility.swift b/EhPanda/App/Utility.swift index 81dde965..687b78d6 100644 --- a/EhPanda/App/Utility.swift +++ b/EhPanda/App/Utility.swift @@ -329,9 +329,15 @@ func setPasteboardChangeCount(with value: Int) { UserDefaults.standard.set(value, forKey: "PasteboardChangeCount") } -func postSlideMenuShouldCloseNotification() { +func postShouldShowSlideMenuNotification() { NotificationCenter.default.post( - name: NSNotification.Name("SlideMenuShouldClose"), + name: NSNotification.Name("ShouldShowSlideMenu"), + object: nil + ) +} +func postShouldHideSlideMenuNotification() { + NotificationCenter.default.post( + name: NSNotification.Name("ShouldHideSlideMenu"), object: nil ) } diff --git a/EhPanda/View/Home/FilterView.swift b/EhPanda/View/Home/FilterView.swift index 5726c492..46b4251e 100644 --- a/EhPanda/View/Home/FilterView.swift +++ b/EhPanda/View/Home/FilterView.swift @@ -27,55 +27,57 @@ struct FilterView: View, StoreAccessor { // MARK: FilterView var body: some View { - Form { - Section { - CategoryView(bindings: categoryBindings) - Button(action: onResetButtonTap) { - Text("Reset filters") - .foregroundStyle(.red) - } - Toggle("Advanced settings", isOn: filterBinding.advanced) - } - Group { - Section(header: Text("Advanced")) { - 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) - } + NavigationView { + Form { Section { - Toggle("Set minimum rating", isOn: filterBinding.minRatingActivated) - MinimumRatingSetter(minimum: filterBinding.minRating) - .disabled(!filter.minRatingActivated) - Toggle("Set pages range", isOn: filterBinding.pageRangeActivated) - PagesRangeSetter( - lowerBound: filterBinding.pageLowerBound, - upperBound: filterBinding.pageUpperBound - ) - .disabled(!filter.pageRangeActivated) + CategoryView(bindings: categoryBindings) + Button(action: onResetButtonTap) { + Text("Reset filters") + .foregroundStyle(.red) + } + Toggle("Advanced settings", isOn: filterBinding.advanced) } - Section(header: Text("Default Filter")) { - Toggle("Disable language filter", isOn: filterBinding.disableLanguage) - Toggle("Disable uploader filter", isOn: filterBinding.disableUploader) - Toggle("Disable tags filter", isOn: filterBinding.disableTags) + Group { + Section(header: Text("Advanced")) { + 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) + } + Section { + Toggle("Set minimum rating", isOn: filterBinding.minRatingActivated) + MinimumRatingSetter(minimum: filterBinding.minRating) + .disabled(!filter.minRatingActivated) + Toggle("Set pages range", isOn: filterBinding.pageRangeActivated) + PagesRangeSetter( + lowerBound: filterBinding.pageLowerBound, + upperBound: filterBinding.pageUpperBound + ) + .disabled(!filter.pageRangeActivated) + } + Section(header: Text("Default Filter")) { + Toggle("Disable language filter", isOn: filterBinding.disableLanguage) + Toggle("Disable uploader filter", isOn: filterBinding.disableUploader) + Toggle("Disable tags filter", isOn: filterBinding.disableTags) + } } + .disabled(!filter.advanced) } - .disabled(!filter.advanced) - } - .actionSheet(item: environmentBinding.filterViewActionSheetState) { item in - switch item { - case .resetFilters: - return ActionSheet(title: Text("Are you sure to reset?"), buttons: [ - .destructive(Text("Reset"), action: resetFilters), - .cancel() - ]) + .actionSheet(item: environmentBinding.filterViewActionSheetState) { item in + switch item { + case .resetFilters: + return ActionSheet(title: Text("Are you sure to reset?"), buttons: [ + .destructive(Text("Reset"), action: resetFilters), + .cancel() + ]) + } } + .navigationBarTitle("Filters") } - .navigationBarTitle("Filters") } } diff --git a/EhPanda/View/Home/Home.swift b/EhPanda/View/Home/Home.swift index 8f2ed67b..f97d0529 100644 --- a/EhPanda/View/Home/Home.swift +++ b/EhPanda/View/Home/Home.swift @@ -76,9 +76,7 @@ struct Home: View, StoreAccessor { NotificationCenter.default.publisher( for: UIApplication.didBecomeActiveNotification ) - ) { _ in - onWidthChange() - } + ) { _ in onWidthChange() } .onReceive( NotificationCenter.default.publisher( for: UIDevice.orientationDidChangeNotification @@ -90,18 +88,19 @@ struct Home: View, StoreAccessor { } .onReceive( NotificationCenter.default.publisher( - for: NSNotification.Name("SlideMenuShouldClose") + for: NSNotification.Name("ShouldShowSlideMenu") ) - ) { _ in - onSlideMenuShouldCloseNotificationReceive() - } + ) { _ in performTransition(offset: 0) } + .onReceive( + NotificationCenter.default.publisher( + for: NSNotification.Name("ShouldHideSlideMenu") + ) + ) { _ in performTransition(offset: -width) } .onReceive( NotificationCenter.default.publisher( for: NSNotification.Name("BypassesSNIFilteringDidChange") ) - ) { _ in - toggleDomainFronting() - } + ) { _ in toggleDomainFronting() } } } @@ -128,9 +127,6 @@ private extension Home { postAppWidthDidChangeNotification() } } - func onSlideMenuShouldCloseNotificationReceive() { - performTransition(offset: -width) - } func toggleDomainFronting() { if setting.bypassesSNIFiltering { URLProtocol.registerClass(DFURLProtocol.self) diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index a2a44f31..8a6177f9 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -59,22 +59,40 @@ struct HomeView: View, StoreAccessor { .onSubmit(of: .search, onSearchSubmit) .navigationBarTitle(navigationBarTitle) .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(action: postShouldShowSlideMenuNotification) { + Image(systemName: "line.3.horizontal") + .foregroundColor(.secondary) + } + } ToolbarItem(placement: .navigationBarTrailing) { - Menu { - if environment.homeListType == .favorites { - favoritesMenuContent - } else if environment.homeListType == .toplists { - toplistsMenuContent + HStack { + Menu { + if environment.homeListType == .favorites { + favoritesMenuContent + } else if environment.homeListType == .toplists { + toplistsMenuContent + } + } label: { + Image(systemName: "square.3.stack.3d.top.fill") + .symbolRenderingMode(.hierarchical) + .foregroundColor(.primary) + } + .opacity( + [.favorites, .toplists] + .contains(environment.homeListType) ? 1 : 0 + ) + Menu { + Button(action: toggleFilter) { + Image(systemName: "line.3.horizontal.decrease.circle") + Text("Filters") + } + } label: { + Image(systemName: "ellipsis.circle") + .symbolRenderingMode(.hierarchical) + .foregroundColor(.primary) } - } label: { - Image(systemName: "square.3.stack.3d.top.fill") - .symbolRenderingMode(.hierarchical) - .foregroundColor(.primary) } - .opacity( - [.favorites, .toplists] - .contains(environment.homeListType) ? 1 : 0 - ) } } } @@ -87,9 +105,7 @@ struct HomeView: View, StoreAccessor { case .setting: SettingView().tint(accentColor) case .filter: - NavigationView { - FilterView().tint(accentColor) - } + FilterView().tint(accentColor) case .newDawn: NewDawnView(greeting: greeting) } @@ -277,8 +293,8 @@ private extension HomeView { } } -// MARK: Private Methods private extension HomeView { + // MARK: Life Cycle func onStartTasks() { detectPasteboard() fetchGreetingIfNeeded() @@ -444,7 +460,7 @@ private extension HomeView { store.dispatch(.toggleHomeViewSheet(state: nil)) } if !environment.isSlideMenuClosed { - postSlideMenuShouldCloseNotification() + postShouldHideSlideMenuNotification() } } func translateTag(text: String) -> String { @@ -460,7 +476,7 @@ private extension HomeView { return translator.translate(text: text) } - // MARK: Fetch Methods + // MARK: Dispatch Methods func fetchFrontpageItems() { store.dispatch(.fetchFrontpageItems) } @@ -546,6 +562,9 @@ private extension HomeView { fetchToplistsItems() } } + func toggleFilter() { + store.dispatch(.toggleHomeViewSheet(state: .filter)) + } } // MARK: Definition diff --git a/EhPanda/View/Setting/AppearanceSettingView.swift b/EhPanda/View/Setting/AppearanceSettingView.swift index e459e277..da9cfbe2 100644 --- a/EhPanda/View/Setting/AppearanceSettingView.swift +++ b/EhPanda/View/Setting/AppearanceSettingView.swift @@ -18,19 +18,6 @@ struct AppearanceSettingView: View, StoreAccessor { private var selectedIcon: IconType { store.appState.settings.setting.appIconType } - private var isTranslatesTagsVisible: Bool { - guard let preferredLanguage = - Locale.preferredLanguages.first - else { return false } - let isLanguageSupported = - TranslatableLanguage.allCases - .map(\.languageCode).contains( - where: preferredLanguage.contains - ) - let isTranslationsPrepared = - !settings.tagTranslator.contents.isEmpty - return isLanguageSupported && isTranslationsPrepared - } var body: some View { Form { @@ -52,11 +39,6 @@ struct AppearanceSettingView: View, StoreAccessor { ColorPicker("Tint Color", selection: settingBinding.accentColor) Button("App Icon", action: onAppIconButtonTap) .foregroundStyle(.primary).withArrow() - if isTranslatesTagsVisible { - Toggle(isOn: settingBinding.translatesTags) { - Text("Translates tags") - } - } } Section(header: Text("List")) { HStack { diff --git a/EhPanda/View/Setting/GeneralSettingView.swift b/EhPanda/View/Setting/GeneralSettingView.swift index 0b27ab06..04838b58 100644 --- a/EhPanda/View/Setting/GeneralSettingView.swift +++ b/EhPanda/View/Setting/GeneralSettingView.swift @@ -13,6 +13,20 @@ struct GeneralSettingView: View, StoreAccessor { @EnvironmentObject var store: Store @State private var passcodeNotSet = false + private var isTranslatesTagsVisible: Bool { + guard let preferredLanguage = + Locale.preferredLanguages.first + else { return false } + let isLanguageSupported = + TranslatableLanguage.allCases + .map(\.languageCode).contains( + where: preferredLanguage.contains + ) + let isTranslationsPrepared = + !settings.tagTranslator.contents.isEmpty + return isLanguageSupported && isTranslationsPrepared + } + var body: some View { Form { Section { @@ -22,8 +36,12 @@ struct GeneralSettingView: View, StoreAccessor { Button(language, action: toSettingLanguage) .foregroundStyle(.tint) } + if isTranslatesTagsVisible { + Toggle(isOn: settingBinding.translatesTags) { + Text("Translates tags") + } + } NavigationLink("Logs", destination: LogsView()) - NavigationLink("Filters", destination: FilterView()) } Section(header: Text("Navigation")) { Toggle( From 53a68c6252ad01614649d2af11fa572d5271b844 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, 25 Sep 2021 11:50:27 +0800 Subject: [PATCH 2/8] feat: Make AutoLock functionable after exiting #36 --- EhPanda/View/Home/AuthView.swift | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/EhPanda/View/Home/AuthView.swift b/EhPanda/View/Home/AuthView.swift index 021b3cbc..2c42e9f9 100644 --- a/EhPanda/View/Home/AuthView.swift +++ b/EhPanda/View/Home/AuthView.swift @@ -9,8 +9,9 @@ import SwiftUI struct AuthView: View, StoreAccessor { @EnvironmentObject var store: Store - @State private var enterBackgroundDate: Date? + @State private var isLaunchingApp = true @Binding private var blurRadius: CGFloat + @State private var enterBackgroundDate: Date? init(blurRadius: Binding) { _blurRadius = blurRadius @@ -20,36 +21,29 @@ struct AuthView: View, StoreAccessor { var body: some View { Image(systemName: "lock.fill") .font(.system(size: 80)) + .onAppear(perform: onAppear) .opacity(isAppUnlocked ? 0 : 1) .onTapGesture(perform: authenticate) .onReceive( NotificationCenter.default.publisher( for: UIApplication.willResignActiveNotification ) - ) { _ in - onResignActive() - } + ) { _ in onResignActive() } .onReceive( NotificationCenter.default.publisher( for: UIApplication.didBecomeActiveNotification ) - ) { _ in - onDidBecomeActive() - } + ) { _ in onDidBecomeActive() } .onReceive( NotificationCenter.default.publisher( for: UIApplication.didEnterBackgroundNotification ) - ) { _ in - onDidEnterBackground() - } + ) { _ in onDidEnterBackground() } .onReceive( NotificationCenter.default.publisher( for: UIApplication.willEnterForegroundNotification ) - ) { _ in - onWillEnterForeground() - } + ) { _ in onWillEnterForeground() } } } @@ -58,6 +52,13 @@ private extension AuthView { autoLockPolicy.value } + func onAppear() { + guard autoLockPolicy != .never + && isLaunchingApp + else { return } + isLaunchingApp = false + lock() + } func onLockTap() { impactFeedback(style: .soft) authenticate() @@ -94,19 +95,18 @@ private extension AuthView { } store.dispatch(.toggleBlur(effectOn: effectOn)) } - func set(isUnlocked: Bool) { store.dispatch(.toggleApp(unlocked: isUnlocked)) } + func lock() { + set(isUnlocked: false) + setBlur(effectOn: true) + } func lockIfExpired() { if let resignDate = enterBackgroundDate, Date().timeIntervalSince(resignDate) - > Double(autoLockThreshold) - { - set(isUnlocked: false) - setBlur(effectOn: true) - } + > Double(autoLockThreshold) { lock() } enterBackgroundDate = nil } From 77097ff72969a827616d3b1010c3503467d25af3 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, 25 Sep 2021 15:25:33 +0800 Subject: [PATCH 3/8] feat: AutoPlay --- EhPanda/App/de.lproj/Localizable.strings | 11 ++- EhPanda/App/ja.lproj/Localizable.strings | 11 ++- EhPanda/App/ko.lproj/Localizable.strings | 11 ++- EhPanda/App/zh-Hans.lproj/Localizable.strings | 11 ++- EhPanda/App/zh-Hant.lproj/Localizable.strings | 11 ++- EhPanda/DataFlow/AppError.swift | 2 +- EhPanda/Models/Setting.swift | 39 +++++----- EhPanda/View/Home/AuthView.swift | 2 +- EhPanda/View/Reading/ControlPanel.swift | 78 ++++++++++++------- EhPanda/View/Reading/ReadingView.swift | 58 ++++++++++++-- EhPanda/View/Setting/GeneralSettingView.swift | 4 +- 11 files changed, 153 insertions(+), 85 deletions(-) diff --git a/EhPanda/App/de.lproj/Localizable.strings b/EhPanda/App/de.lproj/Localizable.strings index 7bc46d08..abac3f0e 100644 --- a/EhPanda/App/de.lproj/Localizable.strings +++ b/EhPanda/App/de.lproj/Localizable.strings @@ -32,7 +32,7 @@ // MARK: AlertView "Loading..." = "Wird geladen..."; "Login" = "Einloggen"; -"Your search didn't match any docs." = "Deine Suche hat keine Ergebnisse ergeben"; +//"There seems to be nothing here." = ""; "Retry" = "Erneut versuchen"; //"A network error occurred." = ""; //"A parsing error occurred." = ""; @@ -104,6 +104,7 @@ "Post" = "Senden"; // MARK: ReadingView +//"AutoPlay" = ""; //"Reload" = ""; //"Copy" = ""; //"Save" = ""; @@ -403,11 +404,9 @@ // MARK: AutoLockPolicy "Never" = "Nie"; "Instantly" = "Sofort"; -"15 seconds" = "Nach 15 Sekunden"; -"1 minute" = "Nach 1 Minute"; -"5 minutes" = "Nach 5 Minuten"; -"10 minutes" = "Nach 10 Minuten"; -"30 minutes" = "Nach 30 Minuten"; +"%lld seconds" = "Nach %lld Sekunden"; +"%lld minute" = "Nach %lld Minute"; +"%lld minutes" = "Nach %lld Minuten"; // MARK: Language "Other" = "Andere"; diff --git a/EhPanda/App/ja.lproj/Localizable.strings b/EhPanda/App/ja.lproj/Localizable.strings index ba610693..b044a491 100644 --- a/EhPanda/App/ja.lproj/Localizable.strings +++ b/EhPanda/App/ja.lproj/Localizable.strings @@ -32,7 +32,7 @@ // MARK: AlertView "Loading..." = "読み込み中..."; "Login" = "ログイン"; -"Your search didn't match any docs." = "お探しの情報が見つかりませんでした"; +"There seems to be nothing here." = "ここには何もないようです"; "Retry" = "やり直す"; "A network error occurred." = "ネットワーク障害が発生しました"; "A parsing error occurred." = "解析中に問題が発生しました"; @@ -104,6 +104,7 @@ "Post" = "投稿"; // MARK: ReadingView +"AutoPlay" = "自動再生"; "Reload" = "再読み込み"; "Copy" = "コピー"; "Save" = "保存"; @@ -403,11 +404,9 @@ // MARK: AutoLockPolicy "Never" = "なし"; "Instantly" = "すぐに"; -"15 seconds" = "15 秒"; -"1 minute" = "1 分"; -"5 minutes" = "5 分"; -"10 minutes" = "10 分"; -"30 minutes" = "30 分"; +"%lld seconds" = "%lld 秒"; +"%lld minute" = "%lld 分"; +"%lld minutes" = "%lld 分"; // MARK: Language "Other" = "その他"; diff --git a/EhPanda/App/ko.lproj/Localizable.strings b/EhPanda/App/ko.lproj/Localizable.strings index 51684b13..5243c48e 100644 --- a/EhPanda/App/ko.lproj/Localizable.strings +++ b/EhPanda/App/ko.lproj/Localizable.strings @@ -32,7 +32,7 @@ // MARK: AlertView "Loading..." = "로딩 중..."; "Login" = "로그인"; -"Your search didn't match any docs." = "검색 결과가 없어요."; +//"There seems to be nothing here." = ""; "Retry" = "재시도"; "A network error occurred." = "인터넷 접속 오류가 발생했어요."; "A parsing error occurred." = "구분 분석 오류가 발생했어요."; @@ -104,6 +104,7 @@ "Post" = "등록"; // MARK: ReadingView +//"AutoPlay" = ""; "Reload" = "재시도"; "Copy" = "복사"; "Save" = "저장"; @@ -403,11 +404,9 @@ // MARK: AutoLockPolicy "Never" = "안 함"; "Instantly" = "즉시"; -"15 seconds" = "15초"; -"1 minute" = "1분"; -"5 minutes" = "5분"; -"10 minutes" = "10분"; -"30 minutes" = "30분"; +"%lld seconds" = "%lld초"; +"%lld minute" = "%lld분"; +"%lld minutes" = "%lld분"; // MARK: Language "Other" = "기타"; diff --git a/EhPanda/App/zh-Hans.lproj/Localizable.strings b/EhPanda/App/zh-Hans.lproj/Localizable.strings index ee3c6a9f..a57b7fbc 100644 --- a/EhPanda/App/zh-Hans.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hans.lproj/Localizable.strings @@ -32,7 +32,7 @@ // MARK: AlertView "Loading..." = "加载中..."; "Login" = "登录"; -"Your search didn't match any docs." = "未能找到你需要的信息"; +"There seems to be nothing here." = "这里似乎什么也没有"; "Retry" = "重试"; "A network error occurred." = "发生了网络故障"; "A parsing error occurred." = "发生了解析错误"; @@ -104,6 +104,7 @@ "Post" = "发布"; // MARK: ReadingView +"AutoPlay" = "自动播放"; "Reload" = "重新加载"; "Copy" = "复制"; "Save" = "保存"; @@ -403,11 +404,9 @@ // MARK: AutoLockPolicy "Never" = "不锁定"; "Instantly" = "立即"; -"15 seconds" = "15 秒"; -"1 minute" = "1 分钟"; -"5 minutes" = "5 分钟"; -"10 minutes" = "10 分钟"; -"30 minutes" = "30 分钟"; +"%lld seconds" = "%lld 秒"; +"%lld minute" = "%lld 分钟"; +"%lld minutes" = "%lld 分钟"; // MARK: Language "Other" = "其它"; diff --git a/EhPanda/App/zh-Hant.lproj/Localizable.strings b/EhPanda/App/zh-Hant.lproj/Localizable.strings index de2833e3..6ee7e1db 100644 --- a/EhPanda/App/zh-Hant.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hant.lproj/Localizable.strings @@ -32,7 +32,7 @@ // MARK: AlertView "Loading..." = "載入中..."; "Login" = "登入"; -"Your search didn't match any docs." = "未能找到你需要的資訊"; +"There seems to be nothing here." = "這裡似乎什麼也沒有"; "Retry" = "重試"; "A network error occurred." = "網絡發生故障"; //"A parsing error occurred." = ""; @@ -104,6 +104,7 @@ "Post" = "發表"; // MARK: ReadingView +//"AutoPlay" = ""; //"Reload" = ""; //"Copy" = ""; //"Save" = ""; @@ -403,11 +404,9 @@ // MARK: AutoLockPolicy "Never" = "不鎖定"; "Instantly" = "立即"; -"15 seconds" = "15 秒"; -"1 minute" = "1 分鐘"; -"5 minutes" = "5 分鐘"; -"10 minutes" = "10 分鐘"; -"30 minutes" = "30 分鐘"; +"%lld seconds" = "%lld 秒"; +"%lld minute" = "%lld 分鐘"; +"%lld minutes" = "%lld 分鐘"; // MARK: Language "Other" = "其它"; diff --git a/EhPanda/DataFlow/AppError.swift b/EhPanda/DataFlow/AppError.swift index 9d15b93d..e353a64e 100644 --- a/EhPanda/DataFlow/AppError.swift +++ b/EhPanda/DataFlow/AppError.swift @@ -78,7 +78,7 @@ extension AppError: LocalizedError { case .noUpdates: return "" case .notFound: - return "Your search didn't match any docs." + return "There seems to be nothing here." case .unknown: return ["An unknown error occurred.", tryLater] .map(\.localized).joined(separator: "\n") diff --git a/EhPanda/Models/Setting.swift b/EhPanda/Models/Setting.swift index 5222523e..11d8173c 100644 --- a/EhPanda/Models/Setting.swift +++ b/EhPanda/Models/Setting.swift @@ -98,34 +98,37 @@ enum GalleryHost: String, Codable, CaseIterable, Identifiable { } } -enum AutoLockPolicy: String, Codable, CaseIterable, Identifiable { - var id: Int { hashValue } - var value: Int { +enum AutoLockPolicy: Int, Codable, CaseIterable, Identifiable { + var id: Int { rawValue } + + case never = -1 + case instantly = 0 + case sec15 = 15 + case min1 = 60 + case min5 = 300 + case min10 = 600 + case min30 = 1800 +} + +extension AutoLockPolicy { + var descriptionKey: LocalizedStringKey { switch self { case .never: - return -1 + return "Never" case .instantly: - return 0 + return "Instantly" case .sec15: - return 15 + return "\(15) seconds" case .min1: - return 60 + return "\(1) minute" case .min5: - return 300 + return "\(5) minutes" case .min10: - return 600 + return "\(10) minute" case .min30: - return 1800 + return "\(30) minute" } } - - case never = "Never" - case instantly = "Instantly" - case sec15 = "15 seconds" - case min1 = "1 minute" - case min5 = "5 minutes" - case min10 = "10 minutes" - case min30 = "30 minutes" } enum PreferredColorScheme: String, Codable, CaseIterable, Identifiable { diff --git a/EhPanda/View/Home/AuthView.swift b/EhPanda/View/Home/AuthView.swift index 2c42e9f9..9703e52b 100644 --- a/EhPanda/View/Home/AuthView.swift +++ b/EhPanda/View/Home/AuthView.swift @@ -49,7 +49,7 @@ struct AuthView: View, StoreAccessor { private extension AuthView { var autoLockThreshold: Int { - autoLockPolicy.value + autoLockPolicy.rawValue } func onAppear() { diff --git a/EhPanda/View/Reading/ControlPanel.swift b/EhPanda/View/Reading/ControlPanel.swift index d0637950..cc97903e 100644 --- a/EhPanda/View/Reading/ControlPanel.swift +++ b/EhPanda/View/Reading/ControlPanel.swift @@ -13,6 +13,7 @@ struct ControlPanel: View { @Binding private var showsPanel: Bool @Binding private var sliderValue: Float @Binding private var setting: Setting + @Binding private var autoPlayPolicy: AutoPlayPolicy private let currentIndex: Int private let range: ClosedRange private let previews: [Int: String] @@ -25,6 +26,7 @@ struct ControlPanel: View { showsPanel: Binding, sliderValue: Binding, setting: Binding, + autoPlayPolicy: Binding, currentIndex: Int, range: ClosedRange, previews: [Int: String], @@ -36,6 +38,7 @@ struct ControlPanel: View { _showsPanel = showsPanel _sliderValue = sliderValue _setting = setting + _autoPlayPolicy = autoPlayPolicy self.currentIndex = currentIndex self.range = range self.previews = previews @@ -51,6 +54,7 @@ struct ControlPanel: View { title: "\(currentIndex) / " + "\(Int(range.upperBound))", setting: $setting, + autoPlayPolicy: $autoPlayPolicy, settingAction: settingAction, updateSettingAction: updateSettingAction ) @@ -77,6 +81,7 @@ struct ControlPanel: View { private struct UpperPanel: View { @Environment(\.dismiss) var dismissAction @Binding var setting: Setting + @Binding private var autoPlayPolicy: AutoPlayPolicy private let title: String private let settingAction: () -> Void @@ -84,11 +89,13 @@ private struct UpperPanel: View { init( title: String, setting: Binding, + autoPlayPolicy: Binding, settingAction: @escaping () -> Void, updateSettingAction: @escaping (Setting) -> Void ) { self.title = title _setting = setting + _autoPlayPolicy = autoPlayPolicy self.settingAction = settingAction self.updateSettingAction = updateSettingAction } @@ -105,41 +112,58 @@ private struct UpperPanel: View { Slider(value: .constant(0)) .opacity(0) Spacer() - if isLandscape && setting.readingDirection != .vertical { - Menu { - Button { - var setting = setting - setting.enablesDualPageMode.toggle() - updateSettingAction(setting) - } label: { - Text("Dual-page mode") - if setting.enablesDualPageMode { - Image(systemName: "checkmark") + HStack(spacing: 20) { + if isLandscape && setting.readingDirection != .vertical { + Menu { + Button { + var setting = setting + setting.enablesDualPageMode.toggle() + updateSettingAction(setting) + } label: { + Text("Dual-page mode") + if setting.enablesDualPageMode { + Image(systemName: "checkmark") + } } - } - Button { - var setting = setting - setting.exceptCover.toggle() - updateSettingAction(setting) + Button { + var setting = setting + setting.exceptCover.toggle() + updateSettingAction(setting) + } label: { + Text("Except the cover") + if setting.exceptCover { + Image(systemName: "checkmark") + } + } + .disabled(!setting.enablesDualPageMode) } label: { - Text("Except the cover") - if setting.exceptCover { - Image(systemName: "checkmark") + Image(systemName: "rectangle.split.2x1") + .symbolVariant(setting.enablesDualPageMode ? .fill : .none) + } + } + Menu { + Text("AutoPlay").foregroundColor(.secondary) + ForEach(AutoPlayPolicy.allCases) { policy in + Button { + autoPlayPolicy = policy + } label: { + Text(policy.descriptionKey) + if autoPlayPolicy == policy { + Image(systemName: "checkmark") + } } } - .disabled(!setting.enablesDualPageMode) } label: { - Image(systemName: "rectangle.split.2x1") - .symbolVariant(setting.enablesDualPageMode ? .fill : .none) + Image(systemName: "clock") } - .font(.title2) - .padding() - } - Button(action: settingAction) { - Image(systemName: "gear") + .frame(height: 25) // workaround + .clipped() + Button(action: settingAction) { + Image(systemName: "gear") + } + .padding(.trailing, 20) } .font(.title2) - .padding(.trailing, 20) } Text(title).bold() .lineLimit(1) diff --git a/EhPanda/View/Reading/ReadingView.swift b/EhPanda/View/Reading/ReadingView.swift index e2fa6dad..f7fac782 100644 --- a/EhPanda/View/Reading/ReadingView.swift +++ b/EhPanda/View/Reading/ReadingView.swift @@ -28,6 +28,9 @@ struct ReadingView: View, StoreAccessor, PersistenceAccessor { @State private var sliderValue: Float = 1 @State private var sheetState: ReadingViewSheetState? + @State private var autoPlayTimer: Timer? + @State private var autoPlayPolicy: AutoPlayPolicy = .never + @State private var scaleAnchor: UnitPoint = .center @State private var scale: CGFloat = 1 @State private var baseScale: CGFloat = 1 @@ -202,6 +205,7 @@ struct ReadingView: View, StoreAccessor, PersistenceAccessor { showsPanel: $showsPanel, sliderValue: $sliderValue, setting: $store.appState.settings.setting, + autoPlayPolicy: $autoPlayPolicy, currentIndex: mappingFromPager(index: page.index), range: 1...Float(pageCount), previews: detailInfo.previews[gid] ?? [:], @@ -229,6 +233,7 @@ struct ReadingView: View, StoreAccessor, PersistenceAccessor { .allowsHitTesting(environment.isAppUnlocked) } .onChange(of: page.index, perform: onPagerIndexChanged) + .onChange(of: autoPlayPolicy, perform: onAutoPlayPolicyChanged) .onChange(of: setting.exceptCover, perform: onControlPanelSliderChanged) .onChange(of: setting.readingDirection, perform: onControlPanelSliderChanged) .onChange(of: setting.enablesDualPageMode, perform: onControlPanelSliderChanged) @@ -264,16 +269,12 @@ struct ReadingView: View, StoreAccessor, PersistenceAccessor { NotificationCenter.default.publisher( for: UIApplication.willResignActiveNotification ) - ) { _ in - onEndTasks() - } + ) { _ in onEndTasks() } .onReceive( NotificationCenter.default.publisher( for: UIApplication.willTerminateNotification ) - ) { _ in - onEndTasks() - } + ) { _ in onEndTasks() } } } @@ -300,6 +301,7 @@ private extension ReadingView { } func onEndTasks() { saveReadingProgress() + autoPlayPolicy = .never setOrientation(allowsLandscape: false) } func setOrientation(allowsLandscape: Bool, shouldChangeOrientation: Bool = false) { @@ -340,6 +342,27 @@ private extension ReadingView { } } } + func onAutoPlayPolicyChanged(newPolicy: AutoPlayPolicy) { + autoPlayTimer?.invalidate() + guard newPolicy != .never else { return } + autoPlayTimer = Timer.scheduledTimer( + withTimeInterval: TimeInterval(newPolicy.rawValue), + repeats: true, block: onAutoPlayTimerFired + ) + } + func onAutoPlayTimerFired(_: Timer) { + let distance = isLandscape + && setting.enablesDualPageMode + && setting.readingDirection != .vertical ? 2 : 1 + + guard Int(sliderValue) + distance <= pageCount else { + autoPlayPolicy = .never + return + } + + sliderValue += Float(distance) + onControlPanelSliderChanged() + } // MARK: Progress func saveReadingProgress() { @@ -402,6 +425,7 @@ private extension ReadingView { func toggleSetting() { sheetState = .setting + autoPlayPolicy = .never impactFeedback(style: .light) } func dismissSetting() { @@ -768,3 +792,25 @@ struct ReadingView_Previews: PreviewProvider { return ReadingView(gid: "").environmentObject(Store.preview) } } + +enum AutoPlayPolicy: Int, CaseIterable, Identifiable { + var id: Int { rawValue } + + case never = -1 + case sec1 = 1 + case sec2 = 2 + case sec3 = 3 + case sec4 = 4 + case sec5 = 5 +} + +extension AutoPlayPolicy { + var descriptionKey: LocalizedStringKey { + switch self { + case .never: + return "Never" + default: + return "\(rawValue) seconds" + } + } +} diff --git a/EhPanda/View/Setting/GeneralSettingView.swift b/EhPanda/View/Setting/GeneralSettingView.swift index 04838b58..0117967a 100644 --- a/EhPanda/View/Setting/GeneralSettingView.swift +++ b/EhPanda/View/Setting/GeneralSettingView.swift @@ -61,10 +61,10 @@ struct GeneralSettingView: View, StoreAccessor { .opacity((passcodeNotSet && setting.autoLockPolicy != .never) ? 1 : 0) Picker( selection: settingBinding.autoLockPolicy, - label: Text(setting.autoLockPolicy.rawValue.localized) + label: Text(setting.autoLockPolicy.descriptionKey) ) { ForEach(AutoLockPolicy.allCases) { policy in - Text(policy.rawValue.localized).tag(policy) + Text(policy.descriptionKey).tag(policy) } } .pickerStyle(.menu) From 73944d230233bff5e44e14f1174d2acd150d9c77 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, 25 Sep 2021 15:45:52 +0800 Subject: [PATCH 4/8] fix: Thumbnail display mode ignored tags maximum setting --- EhPanda/View/Tools/GalleryThumbnailCell.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/EhPanda/View/Tools/GalleryThumbnailCell.swift b/EhPanda/View/Tools/GalleryThumbnailCell.swift index 2029f509..f725e2dc 100644 --- a/EhPanda/View/Tools/GalleryThumbnailCell.swift +++ b/EhPanda/View/Tools/GalleryThumbnailCell.swift @@ -64,7 +64,7 @@ struct GalleryThumbnailCell: View { TagCloudView( tag: GalleryTag( category: .artist, - content: gallery.tags + content: tags ), font: .caption2, textColor: .secondary, @@ -113,6 +113,15 @@ private extension GalleryThumbnailCell { ? Color(.systemGray5) : Color(.systemGray4) } + var tags: [String] { + if setting.summaryRowTagsMaximum > 0 { + return Array( + gallery.tags.prefix(setting.summaryRowTagsMaximum) + ) + } else { + return gallery.tags + } + } } struct GalleryThumbnailCell_Previews: PreviewProvider { From 2f5b3aabed6582e77b0f07531879f6fa02682840 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, 25 Sep 2021 20:17:51 +0800 Subject: [PATCH 5/8] feat: Quick search --- EhPanda.xcodeproj/project.pbxproj | 8 +- EhPanda/App/de.lproj/Localizable.strings | 3 + EhPanda/App/ja.lproj/Localizable.strings | 3 + EhPanda/App/ko.lproj/Localizable.strings | 3 + EhPanda/App/zh-Hans.lproj/Localizable.strings | 3 + EhPanda/App/zh-Hant.lproj/Localizable.strings | 3 + EhPanda/DataFlow/AppAction.swift | 4 + EhPanda/DataFlow/AppState.swift | 16 ++ EhPanda/DataFlow/Store.swift | 8 + .../MODefinition/AppEnvMO+CoreDataClass.swift | 5 +- .../AppEnvMO+CoreDataProperties.swift | 1 + .../Model.xcdatamodeld/.xccurrentversion | 2 +- .../Model 4.xcdatamodel/contents | 62 ++++++++ EhPanda/Models/Misc.swift | 5 + EhPanda/View/Home/HomeView.swift | 10 ++ EhPanda/View/Home/QuickSearchView.swift | 143 ++++++++++++++++++ EhPanda/View/Setting/AccountSettingView.swift | 5 +- 17 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 EhPanda/Database/Model.xcdatamodeld/Model 4.xcdatamodel/contents create mode 100644 EhPanda/View/Home/QuickSearchView.swift diff --git a/EhPanda.xcodeproj/project.pbxproj b/EhPanda.xcodeproj/project.pbxproj index e70db4e2..cb9df685 100644 --- a/EhPanda.xcodeproj/project.pbxproj +++ b/EhPanda.xcodeproj/project.pbxproj @@ -85,6 +85,7 @@ ABE1867826A1733000689FDC /* LaboratorySettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABE1867726A1733000689FDC /* LaboratorySettingView.swift */; }; ABE1867F26A18DD000689FDC /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = ABE1867E26A18DD000689FDC /* SwiftyBeaver */; }; ABE2AE752699F238001D47AA /* AppEnvStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABE2AE742699F238001D47AA /* AppEnvStorage.swift */; }; + ABE9401526FF158D0085E158 /* QuickSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABE9401426FF158D0085E158 /* QuickSearchView.swift */; }; ABEA1FE625A9B40B002966B9 /* Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABEA1FE525A9B40B002966B9 /* Setting.swift */; }; ABEE0AFA2595C6F800C997AE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = ABEE0AFC2595C6F800C997AE /* Localizable.strings */; }; ABF313A525B1AB6600D47A2F /* Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABF313A425B1AB6600D47A2F /* Misc.swift */; }; @@ -238,6 +239,8 @@ ABE2AE742699F238001D47AA /* AppEnvStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEnvStorage.swift; sourceTree = ""; }; ABE9376C265DCD9400EA8B30 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; ABE9376D265DCD9400EA8B30 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; + ABE9401426FF158D0085E158 /* QuickSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSearchView.swift; sourceTree = ""; }; + ABE9401626FF2E610085E158 /* Model 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 4.xcdatamodel"; sourceTree = ""; }; ABEA1FE525A9B40B002966B9 /* Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = ""; }; ABEE0AFB2595C6F800C997AE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; ABEE0AFE2595C73D00C997AE /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -563,6 +566,7 @@ ABF45AC225F3313D00ECB568 /* HomeView.swift */, ABF45AC125F3313D00ECB568 /* FilterView.swift */, ABF45AC425F3313D00ECB568 /* AuthView.swift */, + ABE9401426FF158D0085E158 /* QuickSearchView.swift */, ); path = Home; sourceTree = ""; @@ -881,6 +885,7 @@ ABAFFE4026A86E3000EE8661 /* MeasureTool.swift in Sources */, AB6DE897268822390087C579 /* LogsView.swift in Sources */, AB7B29F226AC471E00EE1F14 /* MigrationPolicy.swift in Sources */, + ABE9401526FF158D0085E158 /* QuickSearchView.swift in Sources */, ABF45AF025F3313D00ECB568 /* CommentView.swift in Sources */, ABF45AE325F3313D00ECB568 /* SlideMenu.swift in Sources */, ABF45AE225F3313D00ECB568 /* AuthView.swift in Sources */, @@ -1415,11 +1420,12 @@ ABC681F126898D46007BBD69 /* Model.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + ABE9401626FF2E610085E158 /* Model 4.xcdatamodel */, AB543FF126DB7FD9009344C0 /* Model 3.xcdatamodel */, AB48BCF626D2539B0021A06C /* Model 2.xcdatamodel */, ABC681F226898D46007BBD69 /* Model.xcdatamodel */, ); - currentVersion = AB543FF126DB7FD9009344C0 /* Model 3.xcdatamodel */; + currentVersion = ABE9401626FF2E610085E158 /* Model 4.xcdatamodel */; path = Model.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/EhPanda/App/de.lproj/Localizable.strings b/EhPanda/App/de.lproj/Localizable.strings index abac3f0e..e5729bef 100644 --- a/EhPanda/App/de.lproj/Localizable.strings +++ b/EhPanda/App/de.lproj/Localizable.strings @@ -352,6 +352,9 @@ "GAINCONTENT_AND" = " und "; "GAINCONTENT_END" = "!"; +// MARK: QuickSearchView +//"Quick search" = ""; + // MARK: HomeListType "Search" = "Suche"; "Frontpage" = "Startseite"; diff --git a/EhPanda/App/ja.lproj/Localizable.strings b/EhPanda/App/ja.lproj/Localizable.strings index b044a491..25373e74 100644 --- a/EhPanda/App/ja.lproj/Localizable.strings +++ b/EhPanda/App/ja.lproj/Localizable.strings @@ -352,6 +352,9 @@ "GAINCONTENT_AND" = "と"; "GAINCONTENT_END" = "を手に入れた!"; +// MARK: QuickSearchView +"Quick search" = "クイック検索"; + // MARK: HomeListType "Search" = "検索"; "Frontpage" = "ホーム"; diff --git a/EhPanda/App/ko.lproj/Localizable.strings b/EhPanda/App/ko.lproj/Localizable.strings index 5243c48e..5cab3f39 100644 --- a/EhPanda/App/ko.lproj/Localizable.strings +++ b/EhPanda/App/ko.lproj/Localizable.strings @@ -352,6 +352,9 @@ "GAINCONTENT_AND" = "과"; "GAINCONTENT_END" = "획득했어요!"; +// MARK: QuickSearchView +//"Quick search" = ""; + // MARK: HomeListType "Search" = "검색"; "Frontpage" = "프론트"; diff --git a/EhPanda/App/zh-Hans.lproj/Localizable.strings b/EhPanda/App/zh-Hans.lproj/Localizable.strings index a57b7fbc..4071cac2 100644 --- a/EhPanda/App/zh-Hans.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hans.lproj/Localizable.strings @@ -352,6 +352,9 @@ "GAINCONTENT_AND" = "和"; "GAINCONTENT_END" = "!"; +// MARK: QuickSearchView +"Quick search" = "快速搜索"; + // MARK: HomeListType "Search" = "搜索"; "Frontpage" = "主页"; diff --git a/EhPanda/App/zh-Hant.lproj/Localizable.strings b/EhPanda/App/zh-Hant.lproj/Localizable.strings index 6ee7e1db..997f0467 100644 --- a/EhPanda/App/zh-Hant.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hant.lproj/Localizable.strings @@ -352,6 +352,9 @@ "GAINCONTENT_AND" = "和"; "GAINCONTENT_END" = "!"; +// MARK: QuickSearchView +"Quick search" = "快速搜尋"; + // MARK: HomeListType "Search" = "搜尋"; "Frontpage" = "主頁"; diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index 9075756f..7a427c18 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -25,6 +25,10 @@ enum AppAction { case fulfillGalleryPreviews(gid: String) case fulfillGalleryContents(gid: String) case updatePendingJumpInfos(gid: String, pageIndex: Int?, commentID: String?) + case appendQuickSearchWord + case deleteQuickSearchWord(offsets: IndexSet) + case modifyQuickSearchWord(newWord: QuickSearchWord) + case moveQuickSearchWord(source: IndexSet, destination: Int) case toggleApp(unlocked: Bool) case toggleBlur(effectOn: Bool) diff --git a/EhPanda/DataFlow/AppState.swift b/EhPanda/DataFlow/AppState.swift index a76a0e99..403cf375 100644 --- a/EhPanda/DataFlow/AppState.swift +++ b/EhPanda/DataFlow/AppState.swift @@ -139,6 +139,8 @@ extension AppState { @AppEnvStorage(type: [String].self, key: "historyKeywords") var historyKeywords: [String] + @AppEnvStorage(type: [QuickSearchWord].self, key: "quickSearchWords") + var quickSearchWords: [QuickSearchWord] func insertGalleries(stored: inout [Gallery], new: [Gallery]) { new.forEach { gallery in @@ -188,6 +190,20 @@ extension AppState { } self.historyKeywords = historyKeywords } + mutating func appendQuickSearchWord() { + quickSearchWords.append(QuickSearchWord(content: "")) + } + mutating func deleteQuickSearchWords(offsets: IndexSet) { + quickSearchWords.remove(atOffsets: offsets) + } + mutating func moveQuickSearchWords(source: IndexSet, destination: Int) { + quickSearchWords.move(fromOffsets: source, toOffset: destination) + } + mutating func modifyQuickSearchWord(newWord: QuickSearchWord) { + if let index = quickSearchWords.firstIndex( + where: { word in word.id == newWord.id } + ) { quickSearchWords[index] = newWord } + } } // MARK: DetailInfo diff --git a/EhPanda/DataFlow/Store.swift b/EhPanda/DataFlow/Store.swift index f37dbb40..e2beecc8 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -126,6 +126,14 @@ final class Store: ObservableObject { case .updatePendingJumpInfos(let gid, let pageIndex, let commentID): appState.detailInfo.pendingJumpPageIndices[gid] = pageIndex appState.detailInfo.pendingJumpCommentIDs[gid] = commentID + case .appendQuickSearchWord: + appState.homeInfo.appendQuickSearchWord() + case .deleteQuickSearchWord(let offsets): + appState.homeInfo.deleteQuickSearchWords(offsets: offsets) + case .modifyQuickSearchWord(let newWord): + appState.homeInfo.modifyQuickSearchWord(newWord: newWord) + case .moveQuickSearchWord(let source, let destination): + appState.homeInfo.moveQuickSearchWords(source: source, destination: destination) // MARK: App Env case .toggleApp(let unlocked): diff --git a/EhPanda/Database/MODefinition/AppEnvMO+CoreDataClass.swift b/EhPanda/Database/MODefinition/AppEnvMO+CoreDataClass.swift index bf392831..1834c962 100644 --- a/EhPanda/Database/MODefinition/AppEnvMO+CoreDataClass.swift +++ b/EhPanda/Database/MODefinition/AppEnvMO+CoreDataClass.swift @@ -16,7 +16,8 @@ extension AppEnvMO: ManagedObjectProtocol { filter: filter?.toObject() ?? Filter(), setting: setting?.toObject() ?? Setting(), tagTranslator: tagTranslator?.toObject() ?? TagTranslator(), - historyKeywords: historyKeywords?.toObject() ?? [String]() + historyKeywords: historyKeywords?.toObject() ?? [String](), + quickSearchWords: quickSearchWords?.toObject() ?? [QuickSearchWord]() ) } } @@ -31,6 +32,7 @@ extension AppEnv: ManagedObjectConvertible { appEnvMO.setting = setting.toData() appEnvMO.tagTranslator = tagTranslator.toData() appEnvMO.historyKeywords = historyKeywords.toData() + appEnvMO.quickSearchWords = quickSearchWords.toData() return appEnvMO } @@ -42,6 +44,7 @@ struct AppEnv: Codable { let setting: Setting let tagTranslator: TagTranslator let historyKeywords: [String] + let quickSearchWords: [QuickSearchWord] } struct TagTranslator: Codable { diff --git a/EhPanda/Database/MODefinition/AppEnvMO+CoreDataProperties.swift b/EhPanda/Database/MODefinition/AppEnvMO+CoreDataProperties.swift index ebb93949..4bcd4feb 100644 --- a/EhPanda/Database/MODefinition/AppEnvMO+CoreDataProperties.swift +++ b/EhPanda/Database/MODefinition/AppEnvMO+CoreDataProperties.swift @@ -17,4 +17,5 @@ extension AppEnvMO { @NSManaged public var setting: Data? @NSManaged public var tagTranslator: Data? @NSManaged public var historyKeywords: Data? + @NSManaged public var quickSearchWords: Data? } diff --git a/EhPanda/Database/Model.xcdatamodeld/.xccurrentversion b/EhPanda/Database/Model.xcdatamodeld/.xccurrentversion index 419fdc45..ea1cf0bc 100644 --- a/EhPanda/Database/Model.xcdatamodeld/.xccurrentversion +++ b/EhPanda/Database/Model.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Model 3.xcdatamodel + Model 4.xcdatamodel diff --git a/EhPanda/Database/Model.xcdatamodeld/Model 4.xcdatamodel/contents b/EhPanda/Database/Model.xcdatamodeld/Model 4.xcdatamodel/contents new file mode 100644 index 00000000..dae72033 --- /dev/null +++ b/EhPanda/Database/Model.xcdatamodeld/Model 4.xcdatamodel/contents @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EhPanda/Models/Misc.swift b/EhPanda/Models/Misc.swift index c126078c..ca0e5ea4 100644 --- a/EhPanda/Models/Misc.swift +++ b/EhPanda/Models/Misc.swift @@ -85,3 +85,8 @@ struct Greeting: Codable, Equatable { .isEmpty } } + +struct QuickSearchWord: Codable, Identifiable { + var id = UUID().uuidString + let content: String +} diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index 8a6177f9..695e7038 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -87,6 +87,10 @@ struct HomeView: View, StoreAccessor { Image(systemName: "line.3.horizontal.decrease.circle") Text("Filters") } + Button(action: toggleQuickSearch) { + Image(systemName: "magnifyingglass.circle") + Text("Quick search") + } } label: { Image(systemName: "ellipsis.circle") .symbolRenderingMode(.hierarchical) @@ -108,6 +112,8 @@ struct HomeView: View, StoreAccessor { FilterView().tint(accentColor) case .newDawn: NewDawnView(greeting: greeting) + case .quickSearch: + QuickSearchView() } } .accentColor(accentColor) @@ -565,6 +571,9 @@ private extension HomeView { func toggleFilter() { store.dispatch(.toggleHomeViewSheet(state: .filter)) } + func toggleQuickSearch() { + store.dispatch(.toggleHomeViewSheet(state: .quickSearch)) + } } // MARK: Definition @@ -608,6 +617,7 @@ enum HomeViewSheetState: Identifiable { case setting case filter case newDawn + case quickSearch } enum ToplistsType: Int, Codable, CaseIterable, Identifiable { diff --git a/EhPanda/View/Home/QuickSearchView.swift b/EhPanda/View/Home/QuickSearchView.swift new file mode 100644 index 00000000..46c3b929 --- /dev/null +++ b/EhPanda/View/Home/QuickSearchView.swift @@ -0,0 +1,143 @@ +// +// QuickSearchView.swift +// EhPanda +// +// Created by 荒木辰造 on R 3/09/25. +// + +import SwiftUI + +struct QuickSearchView: View, StoreAccessor { + @EnvironmentObject var store: Store + @State private var isEditting = false + @State private var refreshID = UUID().uuidString + + var body: some View { + NavigationView { + ZStack { + List { + ForEach(words) { word in + QuickSearchWordRow( + word: word, + isEditting: $isEditting, + submitID: $refreshID, + searchAction: search, + submitAction: modify + ) + } + .onDelete(perform: delete) + .onMove(perform: move) + } + .id(refreshID) + ErrorView(error: .notFound, retryAction: nil) + .opacity(words.isEmpty ? 1 : 0) + } + .environment(\.editMode, .constant( + isEditting ? .active : .inactive + )) + .navigationTitle("Quick search") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + HStack { + Button(action: append) { + Image(systemName: "plus") + } + .opacity(isEditting ? 1 : 0) + Button { + isEditting.toggle() + } label: { + Image(systemName: "pencil.circle") + .symbolVariant(isEditting ? .fill : .none) + } + } + } + } + } + } +} + +private extension QuickSearchView { + var words: [QuickSearchWord] { + store.appState.homeInfo.quickSearchWords + } + func append() { + store.dispatch(.appendQuickSearchWord) + } + func delete(atOffsets offsets: IndexSet) { + store.dispatch(.deleteQuickSearchWord(offsets: offsets)) + } + func modify(newWord: QuickSearchWord) { + store.dispatch(.modifyQuickSearchWord(newWord: newWord)) + } + func move(from source: IndexSet, to destination: Int) { + refreshID = UUID().uuidString + store.dispatch(.moveQuickSearchWord(source: source, destination: destination)) + } + func search(keyword: String) { + store.dispatch(.toggleHomeViewSheet(state: .none)) + store.dispatch(.toggleHomeList(type: .search)) + store.dispatch(.fetchSearchItems(keyword: keyword)) + } +} + +// MARK: QuickSearchWordRow +private struct QuickSearchWordRow: View { + @FocusState private var isFocused + @State private var editableContent: String + private var plainWord: QuickSearchWord + @Binding private var isEditting: Bool + @Binding private var submitID: String + private var searchAction: (String) -> Void + private var submitAction: (QuickSearchWord) -> Void + + init( + word: QuickSearchWord, + isEditting: Binding, + submitID: Binding, + searchAction: @escaping (String) -> Void, + submitAction: @escaping (QuickSearchWord) -> Void + ) { + _editableContent = State(initialValue: word.content) + + plainWord = word + _isEditting = isEditting + _submitID = submitID + self.searchAction = searchAction + self.submitAction = submitAction + } + + var body: some View { + ZStack { + Button(plainWord.content) { + searchAction(plainWord.content) + } + .withArrow().foregroundColor(.primary) + .opacity(isEditting ? 0 : 1) + TextEditor(text: $editableContent) + .textInputAutocapitalization(.none) + .disableAutocorrection(true) + .opacity(isEditting ? 1 : 0) + .focused($isFocused) + } + .onChange(of: submitID, perform: submit) + .onChange(of: isFocused, perform: submit) + .onChange(of: isEditting, perform: onIsEdittingChanged) + } + + private func onIsEdittingChanged(_: Any? = nil) { + submit() + isFocused = false + } + private func submit(_: Any? = nil) { + guard editableContent != plainWord.content else { return } + submitAction(QuickSearchWord(id: plainWord.id, content: editableContent)) + } +} + +struct QuickSearchView_Previews: PreviewProvider { + static var previews: some View { + QuickSearchView() + .preferredColorScheme(.dark) + .environmentObject(Store.preview) + } +} diff --git a/EhPanda/View/Setting/AccountSettingView.swift b/EhPanda/View/Setting/AccountSettingView.swift index 2e8befcb..a8471c54 100644 --- a/EhPanda/View/Setting/AccountSettingView.swift +++ b/EhPanda/View/Setting/AccountSettingView.swift @@ -44,8 +44,9 @@ struct AccountSettingView: View, StoreAccessor { if didLogin { Group { NavigationLink("Account configuration", destination: EhSettingView()) - Button("Manage tags subscription", action: toggleWebViewMyTags).withArrow() - .disabled(setting.bypassesSNIFiltering) + if !setting.bypassesSNIFiltering { + Button("Manage tags subscription", action: toggleWebViewMyTags).withArrow() + } Toggle( "Show new dawn greeting", isOn: settingBinding.showNewDawnGreeting From 0153c3e128f21af92f504f348a2b90198881291f 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, 26 Sep 2021 00:48:24 +0800 Subject: [PATCH 6/8] feat: Page jump --- EhPanda.xcodeproj/project.pbxproj | 17 +++++ .../xcshareddata/swiftpm/Package.resolved | 9 +++ EhPanda/App/Defaults.swift | 68 ++++++++++------- EhPanda/App/Utility.swift | 1 - EhPanda/App/de.lproj/Localizable.strings | 2 + EhPanda/App/ja.lproj/Localizable.strings | 2 + EhPanda/App/ko.lproj/Localizable.strings | 2 + EhPanda/App/zh-Hans.lproj/Localizable.strings | 2 + EhPanda/App/zh-Hant.lproj/Localizable.strings | 2 + EhPanda/DataFlow/AppAction.swift | 11 +-- EhPanda/DataFlow/AppCommand.swift | 19 +++-- EhPanda/DataFlow/Store.swift | 39 +++++++--- EhPanda/Models/Misc.swift | 6 +- EhPanda/Network/Request.swift | 27 ++++--- EhPanda/View/Home/HomeView.swift | 74 ++++++++++++++++++- EhPanda/View/Reading/ControlPanel.swift | 2 +- EhPanda/View/Setting/LoginView.swift | 2 +- EhPanda/View/Setting/WebView.swift | 2 +- 18 files changed, 220 insertions(+), 67 deletions(-) diff --git a/EhPanda.xcodeproj/project.pbxproj b/EhPanda.xcodeproj/project.pbxproj index cb9df685..e3eeb90d 100644 --- a/EhPanda.xcodeproj/project.pbxproj +++ b/EhPanda.xcodeproj/project.pbxproj @@ -86,6 +86,7 @@ ABE1867F26A18DD000689FDC /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = ABE1867E26A18DD000689FDC /* SwiftyBeaver */; }; ABE2AE752699F238001D47AA /* AppEnvStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABE2AE742699F238001D47AA /* AppEnvStorage.swift */; }; ABE9401526FF158D0085E158 /* QuickSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABE9401426FF158D0085E158 /* QuickSearchView.swift */; }; + ABE9402D26FF89220085E158 /* AlertKit in Frameworks */ = {isa = PBXBuildFile; productRef = ABE9402C26FF89220085E158 /* AlertKit */; }; ABEA1FE625A9B40B002966B9 /* Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABEA1FE525A9B40B002966B9 /* Setting.swift */; }; ABEE0AFA2595C6F800C997AE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = ABEE0AFC2595C6F800C997AE /* Localizable.strings */; }; ABF313A525B1AB6600D47A2F /* Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABF313A425B1AB6600D47A2F /* Misc.swift */; }; @@ -296,6 +297,7 @@ files = ( ABD7005926B1C31500DC59C9 /* Kanna in Frameworks */, AB19D619266E5C6700BA752A /* TTProgressHUD in Frameworks */, + ABE9402D26FF89220085E158 /* AlertKit in Frameworks */, AB0F68AF26A6D92F00AC3A54 /* DeprecatedAPI in Frameworks */, ABB944DD26DBBB1800C365C1 /* Kingfisher in Frameworks */, AB6505A026B0027800F91E9D /* SwiftUIPager in Frameworks */, @@ -679,6 +681,7 @@ ABD4032326B6EC6800001B8C /* WaterfallGrid */, ABAC82FD26BC4A96009F5026 /* OpenCC */, ABB944DC26DBBB1800C365C1 /* Kingfisher */, + ABE9402C26FF89220085E158 /* AlertKit */, ); productName = EhPanda; productReference = ABC3C7542593696C00E0C11B /* EhPanda.app */; @@ -747,6 +750,7 @@ ABD4032226B6EC6800001B8C /* XCRemoteSwiftPackageReference "WaterfallGrid" */, ABAC82FC26BC4866009F5026 /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */, ABB944DB26DBBB1800C365C1 /* XCRemoteSwiftPackageReference "Kingfisher" */, + ABE9402B26FF89220085E158 /* XCRemoteSwiftPackageReference "AlertKit" */, ); productRefGroup = ABC3C7552593696C00E0C11B /* Products */; projectDirPath = ""; @@ -1366,6 +1370,14 @@ kind = branch; }; }; + ABE9402B26FF89220085E158 /* XCRemoteSwiftPackageReference "AlertKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/tatsuz0u/AlertKit.git"; + requirement = { + branch = custom; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1414,6 +1426,11 @@ package = ABE1867D26A18DD000689FDC /* XCRemoteSwiftPackageReference "SwiftyBeaver" */; productName = SwiftyBeaver; }; + ABE9402C26FF89220085E158 /* AlertKit */ = { + isa = XCSwiftPackageProductDependency; + package = ABE9402B26FF89220085E158 /* XCRemoteSwiftPackageReference "AlertKit" */; + productName = AlertKit; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b7361cd8..6d9c7408 100644 --- a/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "AlertKit", + "repositoryURL": "https://github.com/tatsuz0u/AlertKit.git", + "state": { + "branch": "custom", + "revision": "b16168b456545408417d014c702747f3abd46226", + "version": null + } + }, { "package": "BetterCodable", "repositoryURL": "https://github.com/marksands/BetterCodable", diff --git a/EhPanda/App/Defaults.swift b/EhPanda/App/Defaults.swift index 4ab55df1..72695e23 100644 --- a/EhPanda/App/Defaults.swift +++ b/EhPanda/App/Defaults.swift @@ -149,64 +149,78 @@ struct Defaults { // MARK: Request extension Defaults.URL { // Fetch - static func searchList(keyword: String, filter: Filter) -> String { - merge(urls: [ - host, fSearch - + keyword.urlEncoded() - ] - + applyFilters(filter: filter) - ) + static func searchList(keyword: String, filter: Filter, pageNum: Int? = nil) -> String { + var params = [host, fSearch + keyword.urlEncoded()] + if let pageNum = pageNum { params.append(page2 + String(pageNum)) } + return merge(urls: params + applyFilters(filter: filter)) } static func moreSearchList( keyword: String, filter: Filter, - pageNum: String, + pageNum: Int, lastID: String ) -> String { merge( urls: [ host, fSearch + keyword.urlEncoded(), - page2 + pageNum, + page2 + String(pageNum), from + lastID ] + applyFilters(filter: filter) ) } - static func frontpageList() -> String { - host + static func frontpageList(pageNum: Int? = nil) -> String { + if let pageNum = pageNum { + return merge(urls: [host, page2 + String(pageNum)]) + } else { + return host + } } - static func moreFrontpageList(pageNum: String, lastID: String) -> String { - merge(urls: [host, page2 + pageNum, from + lastID]) + static func moreFrontpageList(pageNum: Int, lastID: String) -> String { + merge(urls: [host, page2 + String(pageNum), from + lastID]) } static func popularList() -> String { host + popular } - static func watchedList() -> String { - host + watched + static func watchedList(pageNum: Int? = nil) -> String { + if let pageNum = pageNum { + return merge(urls: [host + watched, page2 + String(pageNum)]) + } else { + return host + watched + } } - static func moreWatchedList(pageNum: String, lastID: String) -> String { - merge(urls: [host + watched, page2 + pageNum, from + lastID]) + static func moreWatchedList(pageNum: Int, lastID: String) -> String { + merge(urls: [host + watched, page2 + String(pageNum), from + lastID]) } - static func favoritesList(favIndex: Int) -> String { + static func favoritesList(favIndex: Int, pageNum: Int? = nil) -> String { + var params = [host + favorites] if favIndex == -1 { - return host + favorites + if pageNum == nil { return params[0] } } else { - return merge(urls: [host + favorites, favcat + "\(favIndex)"]) + params.append(favcat + "\(favIndex)") + } + if let pageNum = pageNum { + params.append(page2 + String(pageNum)) } + return merge(urls: params) } - static func moreFavoritesList(favIndex: Int, pageNum: String, lastID: String) -> String { + static func moreFavoritesList(favIndex: Int, pageNum: Int, lastID: String) -> String { if favIndex == -1 { - return merge(urls: [host + favorites, page2 + pageNum, from + lastID]) + return merge(urls: [host + favorites, page2 + String(pageNum), from + lastID]) } else { - return merge(urls: [host + favorites, favcat + "\(favIndex)", page2 + pageNum, from + lastID]) + return merge(urls: [host + favorites, favcat + "\(favIndex)", page2 + String(pageNum), from + lastID]) } } - static func toplistsList(catIndex: Int) -> String { - merge(urls: [ehentai + toplist, topcat + "\(catIndex)"]) + static func toplistsList(catIndex: Int, pageNum: Int? = nil) -> String { + if let pageNum = pageNum { + return merge(urls: [ehentai + toplist, topcat + "\(catIndex)", page1 + String(pageNum)]) + } else { + return merge(urls: [ehentai + toplist, topcat + "\(catIndex)"]) + } } - static func moreToplistsList(catIndex: Int, pageNum: String) -> String { - merge(urls: [ehentai + toplist, topcat + "\(catIndex)", page1 + pageNum]) + static func moreToplistsList(catIndex: Int, pageNum: Int) -> String { + merge(urls: [ehentai + toplist, topcat + "\(catIndex)", page1 + String(pageNum)]) } static func galleryDetail(url: String) -> String { merge(urls: [url, showComments]) diff --git a/EhPanda/App/Utility.swift b/EhPanda/App/Utility.swift index 687b78d6..c64cf448 100644 --- a/EhPanda/App/Utility.swift +++ b/EhPanda/App/Utility.swift @@ -265,7 +265,6 @@ func handleIncomingURL( completion(false, url, nil, nil) } -@available(*, deprecated, message: "Use @FocusState instead.") func hideKeyboard() { UIApplication.shared.sendAction( #selector(UIResponder.resignFirstResponder), diff --git a/EhPanda/App/de.lproj/Localizable.strings b/EhPanda/App/de.lproj/Localizable.strings index e5729bef..ce9d4cd3 100644 --- a/EhPanda/App/de.lproj/Localizable.strings +++ b/EhPanda/App/de.lproj/Localizable.strings @@ -47,6 +47,8 @@ "BAN_INTERVAL_HOURS" = " hours"; "BAN_INTERVAL_MINUTES" = " minutes"; "BAN_INTERVAL_SECONDS" = " seconds"; +//"Jump page" = ""; +//"Confirm" = ""; // MARK: DetailView "Archive" = "Archiv"; diff --git a/EhPanda/App/ja.lproj/Localizable.strings b/EhPanda/App/ja.lproj/Localizable.strings index 25373e74..6466dfd5 100644 --- a/EhPanda/App/ja.lproj/Localizable.strings +++ b/EhPanda/App/ja.lproj/Localizable.strings @@ -47,6 +47,8 @@ "BAN_INTERVAL_HOURS" = " 時間"; "BAN_INTERVAL_MINUTES" = " 分"; "BAN_INTERVAL_SECONDS" = " 秒"; +"Jump page" = "ページジャンプ"; +"Confirm" = "確認"; // MARK: DetailView "Archive" = "アーカイブ"; diff --git a/EhPanda/App/ko.lproj/Localizable.strings b/EhPanda/App/ko.lproj/Localizable.strings index 5cab3f39..ae410646 100644 --- a/EhPanda/App/ko.lproj/Localizable.strings +++ b/EhPanda/App/ko.lproj/Localizable.strings @@ -47,6 +47,8 @@ "BAN_INTERVAL_HOURS" = " hours"; "BAN_INTERVAL_MINUTES" = " minutes"; "BAN_INTERVAL_SECONDS" = " seconds"; +//"Jump page" = ""; +//"Confirm" = ""; // MARK: DetailView "Archive" = "아카이브"; diff --git a/EhPanda/App/zh-Hans.lproj/Localizable.strings b/EhPanda/App/zh-Hans.lproj/Localizable.strings index 4071cac2..fd7a4858 100644 --- a/EhPanda/App/zh-Hans.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hans.lproj/Localizable.strings @@ -47,6 +47,8 @@ "BAN_INTERVAL_HOURS" = " 小时"; "BAN_INTERVAL_MINUTES" = " 分"; "BAN_INTERVAL_SECONDS" = " 秒"; +"Jump page" = "页码跳转"; +"Confirm" = "确认"; // MARK: DetailView "Archive" = "归档"; diff --git a/EhPanda/App/zh-Hant.lproj/Localizable.strings b/EhPanda/App/zh-Hant.lproj/Localizable.strings index 997f0467..6a04595b 100644 --- a/EhPanda/App/zh-Hant.lproj/Localizable.strings +++ b/EhPanda/App/zh-Hant.lproj/Localizable.strings @@ -47,6 +47,8 @@ "BAN_INTERVAL_HOURS" = " hours"; "BAN_INTERVAL_MINUTES" = " minutes"; "BAN_INTERVAL_SECONDS" = " seconds"; +//"Jump page" = ""; +//"Confirm" = ""; // MARK: DetailView "Archive" = "封存"; diff --git a/EhPanda/DataFlow/AppAction.swift b/EhPanda/DataFlow/AppAction.swift index 7a427c18..acc3153c 100644 --- a/EhPanda/DataFlow/AppAction.swift +++ b/EhPanda/DataFlow/AppAction.swift @@ -43,6 +43,7 @@ enum AppAction { case toggleDetailViewSheet(state: DetailViewSheetState?) case toggleCommentViewSheet(state: CommentViewSheetState?) + case handleJumpPage(index: Int, keyword: String? = nil) case fetchIgneous case fetchTagTranslator case fetchTagTranslatorDone(result: Result) @@ -54,25 +55,25 @@ enum AppAction { case fetchFavoriteNamesDone(result: Result<[Int: String], AppError>) case fetchGalleryItemReverse(url: String, shouldParseGalleryURL: Bool) case fetchGalleryItemReverseDone(carriedValue: String, result: Result) - case fetchSearchItems(keyword: String) + case fetchSearchItems(keyword: String, pageNum: Int? = nil) case fetchSearchItemsDone(result: Result<(PageNumber, [Gallery]), AppError>) case fetchMoreSearchItems(keyword: String) case fetchMoreSearchItemsDone(result: Result<(PageNumber, [Gallery]), AppError>) - case fetchFrontpageItems + case fetchFrontpageItems(pageNum: Int? = nil) case fetchFrontpageItemsDone(result: Result<(PageNumber, [Gallery]), AppError>) case fetchMoreFrontpageItems case fetchMoreFrontpageItemsDone(result: Result<(PageNumber, [Gallery]), AppError>) case fetchPopularItems case fetchPopularItemsDone(result: Result<[Gallery], AppError>) - case fetchWatchedItems + case fetchWatchedItems(pageNum: Int? = nil) case fetchWatchedItemsDone(result: Result<(PageNumber, [Gallery]), AppError>) case fetchMoreWatchedItems case fetchMoreWatchedItemsDone(result: Result<(PageNumber, [Gallery]), AppError>) - case fetchFavoritesItems + case fetchFavoritesItems(pageNum: Int? = nil) case fetchFavoritesItemsDone(carriedValue: Int, result: Result<(PageNumber, [Gallery]), AppError>) case fetchMoreFavoritesItems case fetchMoreFavoritesItemsDone(carriedValue: Int, result: Result<(PageNumber, [Gallery]), AppError>) - case fetchToplistsItems + case fetchToplistsItems(pageNum: Int? = nil) case fetchToplistsItemsDone(carriedValue: Int, result: Result<(PageNumber, [Gallery]), AppError>) case fetchMoreToplistsItems case fetchMoreToplistsItemsDone(carriedValue: Int, result: Result<(PageNumber, [Gallery]), AppError>) diff --git a/EhPanda/DataFlow/AppCommand.swift b/EhPanda/DataFlow/AppCommand.swift index 7bcfccf9..1a0d1ced 100644 --- a/EhPanda/DataFlow/AppCommand.swift +++ b/EhPanda/DataFlow/AppCommand.swift @@ -122,10 +122,11 @@ struct FetchGalleryItemReverseCommand: AppCommand { struct FetchSearchItemsCommand: AppCommand { let keyword: String let filter: Filter + var pageNum: Int? func execute(in store: Store) { let token = SubscriptionToken() - SearchItemsRequest(keyword: keyword, filter: filter) + SearchItemsRequest(keyword: keyword, filter: filter, pageNum: pageNum) .publisher.receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { @@ -181,9 +182,11 @@ struct FetchMoreSearchItemsCommand: AppCommand { } struct FetchFrontpageItemsCommand: AppCommand { + var pageNum: Int? + func execute(in store: Store) { let token = SubscriptionToken() - FrontpageItemsRequest().publisher + FrontpageItemsRequest(pageNum: pageNum).publisher .receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { @@ -231,6 +234,8 @@ struct FetchMoreFrontpageItemsCommand: AppCommand { } struct FetchPopularItemsCommand: AppCommand { + var pageNum: Int? + func execute(in store: Store) { let token = SubscriptionToken() PopularItemsRequest().publisher @@ -252,9 +257,11 @@ struct FetchPopularItemsCommand: AppCommand { } struct FetchWatchedItemsCommand: AppCommand { + var pageNum: Int? + func execute(in store: Store) { let token = SubscriptionToken() - WatchedItemsRequest().publisher + WatchedItemsRequest(pageNum: pageNum).publisher .receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { @@ -303,10 +310,11 @@ struct FetchMoreWatchedItemsCommand: AppCommand { struct FetchFavoritesItemsCommand: AppCommand { let favIndex: Int + var pageNum: Int? func execute(in store: Store) { let token = SubscriptionToken() - FavoritesItemsRequest(favIndex: favIndex) + FavoritesItemsRequest(favIndex: favIndex, pageNum: pageNum) .publisher.receive(on: DispatchQueue.main) .sink { completion in if case .failure(let error) = completion { @@ -360,10 +368,11 @@ struct FetchMoreFavoritesItemsCommand: AppCommand { struct FetchToplistsItemsCommand: AppCommand { let topIndex: Int let catIndex: Int + var pageNum: Int? func execute(in store: Store) { let token = SubscriptionToken() - ToplistsItemsRequest(catIndex: catIndex) + ToplistsItemsRequest(catIndex: catIndex, 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 e2beecc8..ad2ff0d6 100644 --- a/EhPanda/DataFlow/Store.swift +++ b/EhPanda/DataFlow/Store.swift @@ -168,6 +168,23 @@ final class Store: ObservableObject { appState.environment.commentViewSheetState = state // MARK: Fetch Data + case .handleJumpPage(let index, let keyword): + switch appState.environment.homeListType { + case .search: + if let keyword = keyword { + dispatch(.fetchSearchItems(keyword: keyword, pageNum: index)) + } + case .frontpage: + dispatch(.fetchFrontpageItems(pageNum: index)) + case .watched: + dispatch(.fetchWatchedItems(pageNum: index)) + case .favorites: + dispatch(.fetchFavoritesItems(pageNum: index)) + case .toplists: + dispatch(.fetchToplistsItems(pageNum: index)) + case .popular, .downloaded, .history: + break + } case .fetchIgneous: appCommand = FetchIgneousCommand() case .fetchTagTranslator: @@ -270,7 +287,7 @@ final class Store: ObservableObject { dispatch(.updatePendingJumpInfos(gid: carriedValue, pageIndex: nil, commentID: nil)) } - case .fetchSearchItems(let keyword): + case .fetchSearchItems(let keyword, let pageNum): appState.homeInfo.searchLoadError = nil if appState.homeInfo.searchLoading { break } @@ -278,7 +295,7 @@ final class Store: ObservableObject { appState.homeInfo.searchLoading = true let filter = appState.settings.filter - appCommand = FetchSearchItemsCommand(keyword: keyword, filter: filter) + appCommand = FetchSearchItemsCommand(keyword: keyword, filter: filter, pageNum: pageNum) case .fetchSearchItemsDone(let result): appState.homeInfo.searchLoading = false @@ -319,13 +336,13 @@ final class Store: ObservableObject { appState.homeInfo.moreSearchLoadFailed = true } - case .fetchFrontpageItems: + case .fetchFrontpageItems(let pageNum): appState.homeInfo.frontpageLoadError = nil if appState.homeInfo.frontpageLoading { break } appState.homeInfo.frontpagePageNumber.current = 0 appState.homeInfo.frontpageLoading = true - appCommand = FetchFrontpageItemsCommand() + appCommand = FetchFrontpageItemsCommand(pageNum: pageNum) case .fetchFrontpageItemsDone(let result): appState.homeInfo.frontpageLoading = false @@ -379,13 +396,13 @@ final class Store: ObservableObject { appState.homeInfo.popularLoadError = error } - case .fetchWatchedItems: + case .fetchWatchedItems(let pageNum): appState.homeInfo.watchedLoadError = nil if appState.homeInfo.watchedLoading { break } appState.homeInfo.watchedPageNumber.current = 0 appState.homeInfo.watchedLoading = true - appCommand = FetchWatchedItemsCommand() + appCommand = FetchWatchedItemsCommand(pageNum: pageNum) case .fetchWatchedItemsDone(let result): appState.homeInfo.watchedLoading = false @@ -422,7 +439,7 @@ final class Store: ObservableObject { appState.homeInfo.moreWatchedLoadFailed = true } - case .fetchFavoritesItems: + case .fetchFavoritesItems(let pageNum): let favIndex = appState.environment.favoritesIndex appState.homeInfo.favoritesLoadErrors[favIndex] = nil @@ -432,7 +449,7 @@ final class Store: ObservableObject { } appState.homeInfo.favoritesPageNumbers[favIndex]?.current = 0 appState.homeInfo.favoritesLoading[favIndex] = true - appCommand = FetchFavoritesItemsCommand(favIndex: favIndex) + appCommand = FetchFavoritesItemsCommand(favIndex: favIndex, pageNum: pageNum) case .fetchFavoritesItemsDone(let carriedValue, let result): appState.homeInfo.favoritesLoading[carriedValue] = false @@ -474,7 +491,7 @@ final class Store: ObservableObject { appState.homeInfo.moreFavoritesLoading[carriedValue] = true } - case .fetchToplistsItems: + case .fetchToplistsItems(let pageNum): let topType = appState.environment.toplistsType appState.homeInfo.toplistsLoadErrors[topType.rawValue] = nil @@ -485,7 +502,7 @@ final class Store: ObservableObject { appState.homeInfo.toplistsPageNumbers[topType.rawValue]?.current = 0 appState.homeInfo.toplistsLoading[topType.rawValue] = true appCommand = FetchToplistsItemsCommand( - topIndex: topType.rawValue, catIndex: topType.categoryIndex + topIndex: topType.rawValue, catIndex: topType.categoryIndex, pageNum: pageNum ) case .fetchToplistsItemsDone(let carriedValue, let result): appState.homeInfo.toplistsLoading[carriedValue] = false @@ -504,7 +521,7 @@ final class Store: ObservableObject { appState.homeInfo.moreToplistsLoadFailed[topType.rawValue] = false let pageNumber = appState.homeInfo.toplistsPageNumbers[topType.rawValue] - if pageNumber?.current ?? 0 + 1 > pageNumber?.maximum ?? 0 { break } + if (pageNumber?.current ?? 0) + 1 > pageNumber?.maximum ?? 0 { break } if appState.homeInfo.moreToplistsLoading[topType.rawValue] == true { break } appState.homeInfo.moreToplistsLoading[topType.rawValue] = true diff --git a/EhPanda/Models/Misc.swift b/EhPanda/Models/Misc.swift index ca0e5ea4..a96fc902 100644 --- a/EhPanda/Models/Misc.swift +++ b/EhPanda/Models/Misc.swift @@ -15,9 +15,13 @@ typealias CurrentGP = String typealias CurrentCredits = String typealias ReloadToken = Any -struct PageNumber { +struct PageNumber: Equatable { var current = 0 var maximum = 0 + + var isSinglePage: Bool { + current == 0 && maximum == 0 + } } struct Greeting: Codable, Equatable { diff --git a/EhPanda/Network/Request.swift b/EhPanda/Network/Request.swift index cdc0a643..86d4db2d 100644 --- a/EhPanda/Network/Request.swift +++ b/EhPanda/Network/Request.swift @@ -162,10 +162,11 @@ struct TagTranslatorRequest { struct SearchItemsRequest { let keyword: String let filter: Filter + var pageNum: Int? var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher( - for: Defaults.URL.searchList(keyword: keyword, filter: filter).safeURL() + for: Defaults.URL.searchList(keyword: keyword, 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)) } @@ -181,7 +182,7 @@ struct MoreSearchItemsRequest { var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher(for: Defaults.URL.moreSearchList( - keyword: keyword, filter: filter, pageNum: "\(pageNum)", lastID: lastID + keyword: keyword, 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)) } @@ -190,8 +191,10 @@ struct MoreSearchItemsRequest { } struct FrontpageItemsRequest { + var pageNum: Int? + var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { - URLSession.shared.dataTaskPublisher(for: Defaults.URL.frontpageList().safeURL()) + URLSession.shared.dataTaskPublisher(for: Defaults.URL.frontpageList(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() @@ -204,7 +207,7 @@ struct MoreFrontpageItemsRequest { var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher(for: Defaults.URL.moreFrontpageList( - pageNum: "\(pageNum)", lastID: lastID + 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)) } @@ -221,8 +224,10 @@ struct PopularItemsRequest { } struct WatchedItemsRequest { + var pageNum: Int? + var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { - URLSession.shared.dataTaskPublisher(for: Defaults.URL.watchedList().safeURL()) + URLSession.shared.dataTaskPublisher(for: Defaults.URL.watchedList(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() @@ -235,7 +240,7 @@ struct MoreWatchedItemsRequest { var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher(for: Defaults.URL.moreWatchedList( - pageNum: "\(pageNum)", lastID: lastID + 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)) } @@ -245,10 +250,11 @@ struct MoreWatchedItemsRequest { struct FavoritesItemsRequest { let favIndex: Int + var pageNum: Int? var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher( - for: Defaults.URL.favoritesList(favIndex: favIndex).safeURL() + for: Defaults.URL.favoritesList(favIndex: favIndex, pageNum: pageNum).safeURL() ) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap { (Parser.parsePageNum(doc: $0), try Parser.parseListItems(doc: $0)) } @@ -263,7 +269,7 @@ struct MoreFavoritesItemsRequest { var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher(for: Defaults.URL.moreFavoritesList( - favIndex: favIndex, pageNum: "\(pageNum)", lastID: lastID + 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)) } @@ -273,10 +279,11 @@ struct MoreFavoritesItemsRequest { struct ToplistsItemsRequest { let catIndex: Int + var pageNum: Int? var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher( - for: Defaults.URL.toplistsList(catIndex: catIndex).safeURL() + for: Defaults.URL.toplistsList(catIndex: catIndex, pageNum: pageNum).safeURL() ) .genericRetry().tryMap { try Kanna.HTML(html: $0.data, encoding: .utf8) } .tryMap { (Parser.parsePageNum(doc: $0), try Parser.parseListItems(doc: $0)) } @@ -290,7 +297,7 @@ struct MoreToplistsItemsRequest { var publisher: AnyPublisher<(PageNumber, [Gallery]), AppError> { URLSession.shared.dataTaskPublisher(for: Defaults.URL.moreToplistsList( - catIndex: catIndex, pageNum: "\(pageNum)" + catIndex: catIndex, pageNum: pageNum ).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/HomeView.swift b/EhPanda/View/Home/HomeView.swift index 695e7038..c40f8ab3 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import AlertKit import TTProgressHUD struct HomeView: View, StoreAccessor { @@ -20,6 +21,9 @@ struct HomeView: View, StoreAccessor { @State private var hudVisible = false @State private var hudConfig = TTProgressHUDConfig() + @State private var alertInput = "" + @StateObject private var alertManager = CustomAlertManager() + // MARK: HomeView var body: some View { NavigationView { @@ -91,6 +95,11 @@ struct HomeView: View, StoreAccessor { Image(systemName: "magnifyingglass.circle") Text("Quick search") } + Button(action: toggleJumpPage) { + Image(systemName: "arrowshape.bounce.forward") + Text("Jump page") + } + .disabled(currentListTypePageNumber.isSinglePage) } label: { Image(systemName: "ellipsis.circle") .symbolRenderingMode(.hierarchical) @@ -103,6 +112,28 @@ struct HomeView: View, StoreAccessor { .onOpenURL(perform: onOpen) .navigationViewStyle(.stack) .onAppear(perform: onStartTasks) + .customAlert(manager: alertManager, widthFactor: isPadWidth ? 0.5 : 1.0, content: { + VStack { + Text("Jump page").bold() + HStack { + let opacity = colorScheme == .light ? 0.15 : 0.1 + TextField(alertInput, text: $alertInput) + .multilineTextAlignment(.center).keyboardType(.numberPad) + .padding(.horizontal, 10).padding(.vertical, 5) + .background(Color.gray.opacity(opacity)) + .cornerRadius(5).frame(width: 75) + Text("-") + Text("\(currentListTypePageNumber.maximum + 1)") + } + .lineLimit(1) + } + }, buttons: [ + .regular { + Text("Confirm").bold() + } action: { + performJumpPage() + } + ]) .sheet(item: environmentBinding.homeViewSheetState) { item in Group { switch item { @@ -124,6 +155,8 @@ struct HomeView: View, StoreAccessor { NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification) ) { _ in onBecomeActive() } .onChange(of: environment.galleryItemReverseLoading, perform: onJumpDetailFetchFinish) + .onChange(of: currentListTypePageNumber, perform: onCurrentListTypePageNumberChanged) + .onChange(of: alertManager.isPresented, perform: onAlertVisibilityChanged) .onChange(of: environment.galleryItemReverseID, perform: onJumpIDChange) .onChange(of: environment.homeListType, perform: onHomeListTypeChange) .onChange(of: homeInfo.searchKeyword, perform: onSearchKeywordChange) @@ -164,6 +197,24 @@ private extension HomeView { return environment.homeListType.rawValue.localized } } + var currentListTypePageNumber: PageNumber { + switch environment.homeListType { + case .search: + return homeInfo.searchPageNumber + case .frontpage: + return homeInfo.frontpagePageNumber + case .watched: + return homeInfo.watchedPageNumber + case .favorites: + let index = environment.favoritesIndex + return homeInfo.favoritesPageNumbers[index] ?? PageNumber() + case .toplists: + let index = environment.toplistsType.rawValue + return homeInfo.toplistsPageNumbers[index] ?? PageNumber() + case .popular, .downloaded, .history: + return PageNumber() + } + } // MARK: View Properties var favoritesMenuContent: some View { @@ -396,6 +447,12 @@ private extension HomeView { func onSuggestionTap(word: String) { store.dispatch(.updateSearchKeyword(text: word)) } + func onAlertVisibilityChanged(_: Bool) { + hideKeyboard() + } + func onCurrentListTypePageNumberChanged(pageNumber: PageNumber) { + alertInput = String(pageNumber.current + 1) + } // MARK: Tool Methods func showHUD() { @@ -484,19 +541,19 @@ private extension HomeView { // MARK: Dispatch Methods func fetchFrontpageItems() { - store.dispatch(.fetchFrontpageItems) + store.dispatch(.fetchFrontpageItems()) } func fetchPopularItems() { store.dispatch(.fetchPopularItems) } func fetchWatchedItems() { - store.dispatch(.fetchWatchedItems) + store.dispatch(.fetchWatchedItems()) } func fetchFavoritesItems() { - store.dispatch(.fetchFavoritesItems) + store.dispatch(.fetchFavoritesItems()) } func fetchToplistsItems() { - store.dispatch(.fetchToplistsItems) + store.dispatch(.fetchToplistsItems()) } func fetchMoreSearchItems() { @@ -574,6 +631,15 @@ private extension HomeView { func toggleQuickSearch() { store.dispatch(.toggleHomeViewSheet(state: .quickSearch)) } + func toggleJumpPage() { + alertManager.show() + } + func performJumpPage() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + if let index = Int(alertInput), index <= currentListTypePageNumber.maximum + 1 + { store.dispatch(.handleJumpPage(index: index - 1, keyword: archivedKeyword)) } + } + } } // MARK: Definition diff --git a/EhPanda/View/Reading/ControlPanel.swift b/EhPanda/View/Reading/ControlPanel.swift index cc97903e..008482d1 100644 --- a/EhPanda/View/Reading/ControlPanel.swift +++ b/EhPanda/View/Reading/ControlPanel.swift @@ -154,7 +154,7 @@ private struct UpperPanel: View { } } } label: { - Image(systemName: "clock") + Image(systemName: "timer") } .frame(height: 25) // workaround .clipped() diff --git a/EhPanda/View/Setting/LoginView.swift b/EhPanda/View/Setting/LoginView.swift index 8d2a61df..ebf1816d 100644 --- a/EhPanda/View/Setting/LoginView.swift +++ b/EhPanda/View/Setting/LoginView.swift @@ -131,7 +131,7 @@ struct LoginView: View, StoreAccessor { } notificFeedback(style: .success) dismissAction.callAsFunction() - store.dispatch(.fetchFrontpageItems) + store.dispatch(.fetchFrontpageItems()) store.dispatch(.verifyEhProfile) store.dispatch(.fetchUserInfo) } diff --git a/EhPanda/View/Setting/WebView.swift b/EhPanda/View/Setting/WebView.swift index 5a2245d8..e44f7c77 100644 --- a/EhPanda/View/Setting/WebView.swift +++ b/EhPanda/View/Setting/WebView.swift @@ -41,7 +41,7 @@ struct WebView: UIViewControllerRepresentable { guard didLogin else { return } let store = self?.parent.store store?.dispatch(.toggleSettingViewSheet(state: nil)) - store?.dispatch(.fetchFrontpageItems) + store?.dispatch(.fetchFrontpageItems()) store?.dispatch(.verifyEhProfile) store?.dispatch(.fetchUserInfo) } From 2d2b283977a2636c58b2855766495f897275b66a 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, 26 Sep 2021 01:13:28 +0800 Subject: [PATCH 7/8] Implement menu in AssociatedView --- .github/workflows/deploy.yml | 2 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- EhPanda/View/Detail/AssociatedView.swift | 58 ++++++++++++++++++- EhPanda/View/Home/HomeView.swift | 23 ++------ EhPanda/View/Setting/EhPandaView.swift | 4 ++ EhPanda/View/Tools/AlertView.swift | 28 +++++++++ 6 files changed, 96 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bf6dd960..cf2dc49e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,7 +6,7 @@ on: types: [closed] env: DEVELOPER_DIR: /Applications/Xcode_13.0.app - APP_VERSION: '1.2.2' + APP_VERSION: '1.3.0' SCHEME_NAME: 'EhPanda' ALTSTORE_JSON_PATH: './AltStore.json' BUILDS_PATH: '/tmp/action-builds' diff --git a/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6d9c7408..8a87ba55 100644 --- a/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/EhPanda.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,7 +6,7 @@ "repositoryURL": "https://github.com/tatsuz0u/AlertKit.git", "state": { "branch": "custom", - "revision": "b16168b456545408417d014c702747f3abd46226", + "revision": "fbac9b1c800929b0938e223498e00e73cd71b582", "version": null } }, diff --git a/EhPanda/View/Detail/AssociatedView.swift b/EhPanda/View/Detail/AssociatedView.swift index a5fe994d..be61d444 100644 --- a/EhPanda/View/Detail/AssociatedView.swift +++ b/EhPanda/View/Detail/AssociatedView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import AlertKit import SwiftyBeaver struct AssociatedView: View, StoreAccessor { @@ -21,6 +22,9 @@ struct AssociatedView: View, StoreAccessor { @State private var associatedItems = [Gallery]() @State private var pageNumber = PageNumber() + @State private var alertInput = "" + @StateObject private var alertManager = CustomAlertManager() + init(keyword: String) { self.title = keyword self.keyword = keyword @@ -45,9 +49,39 @@ struct AssociatedView: View, StoreAccessor { ), prompt: "Search" ) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Menu { + Button(action: toggleFilter) { + Image(systemName: "line.3.horizontal.decrease.circle") + Text("Filters") + } + Button(action: toggleJumpPage) { + Image(systemName: "arrowshape.bounce.forward") + Text("Jump page") + } + .disabled(pageNumber.isSinglePage) + } label: { + Image(systemName: "ellipsis.circle") + .symbolRenderingMode(.hierarchical) + .foregroundColor(.primary) + } + } + } + .customAlert(manager: alertManager, widthFactor: isPadWidth ? 0.5 : 1.0, content: { + PageJumpView(inputText: $alertInput, pageNumber: pageNumber) + }, buttons: [ + .regular { + Text("Confirm") + } action: { + performJumpPage() + } + ]) .navigationBarTitle(title) .onSubmit(of: .search, fetchAssociatedItems) .onAppear(perform: fetchAssociatedItemsIfNeeded) + .onChange(of: pageNumber, perform: onPageNumberChanged) + .onChange(of: alertManager.isPresented, perform: onAlertVisibilityChanged) } } @@ -70,6 +104,12 @@ private extension AssociatedView { fetchMoreAssociatedItems() } } + func onAlertVisibilityChanged(_: Bool) { + hideKeyboard() + } + func onPageNumberChanged(pageNumber: PageNumber) { + alertInput = String(pageNumber.current + 1) + } func fetchAssociatedItemsIfNeeded() { DispatchQueue.main.async { @@ -80,6 +120,9 @@ private extension AssociatedView { } func fetchAssociatedItems() { + fetchAssociatedItems(pageNum: nil) + } + func fetchAssociatedItems(pageNum: Int? = nil) { if !keyword.isEmpty { title = keyword } @@ -92,7 +135,8 @@ private extension AssociatedView { SearchItemsRequest( keyword: keyword.isEmpty ? title : keyword, - filter: filter + filter: filter, + pageNum: pageNum ) .publisher .receive(on: DispatchQueue.main) @@ -206,4 +250,16 @@ private extension AssociatedView { } .seal(in: token) } + func toggleFilter() { + store.dispatch(.toggleHomeViewSheet(state: .filter)) + } + func toggleJumpPage() { + alertManager.show() + } + func performJumpPage() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + if let index = Int(alertInput), index <= pageNumber.maximum + 1 + { fetchAssociatedItems(pageNum: index - 1) } + } + } } diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index c40f8ab3..3b4d6b28 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -113,23 +113,10 @@ struct HomeView: View, StoreAccessor { .navigationViewStyle(.stack) .onAppear(perform: onStartTasks) .customAlert(manager: alertManager, widthFactor: isPadWidth ? 0.5 : 1.0, content: { - VStack { - Text("Jump page").bold() - HStack { - let opacity = colorScheme == .light ? 0.15 : 0.1 - TextField(alertInput, text: $alertInput) - .multilineTextAlignment(.center).keyboardType(.numberPad) - .padding(.horizontal, 10).padding(.vertical, 5) - .background(Color.gray.opacity(opacity)) - .cornerRadius(5).frame(width: 75) - Text("-") - Text("\(currentListTypePageNumber.maximum + 1)") - } - .lineLimit(1) - } + PageJumpView(inputText: $alertInput, pageNumber: currentListTypePageNumber) }, buttons: [ .regular { - Text("Confirm").bold() + Text("Confirm") } action: { performJumpPage() } @@ -155,10 +142,10 @@ struct HomeView: View, StoreAccessor { NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification) ) { _ in onBecomeActive() } .onChange(of: environment.galleryItemReverseLoading, perform: onJumpDetailFetchFinish) - .onChange(of: currentListTypePageNumber, perform: onCurrentListTypePageNumberChanged) .onChange(of: alertManager.isPresented, perform: onAlertVisibilityChanged) .onChange(of: environment.galleryItemReverseID, perform: onJumpIDChange) .onChange(of: environment.homeListType, perform: onHomeListTypeChange) + .onChange(of: currentListTypePageNumber, perform: onPageNumberChanged) .onChange(of: homeInfo.searchKeyword, perform: onSearchKeywordChange) .onChange(of: environment.favoritesIndex, perform: onFavIndexChange) .onChange(of: environment.toplistsType, perform: onTopTypeChange) @@ -450,7 +437,7 @@ private extension HomeView { func onAlertVisibilityChanged(_: Bool) { hideKeyboard() } - func onCurrentListTypePageNumberChanged(pageNumber: PageNumber) { + func onPageNumberChanged(pageNumber: PageNumber) { alertInput = String(pageNumber.current + 1) } @@ -635,7 +622,7 @@ private extension HomeView { alertManager.show() } func performJumpPage() { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if let index = Int(alertInput), index <= currentListTypePageNumber.maximum + 1 { store.dispatch(.handleJumpPage(index: index - 1, keyword: archivedKeyword)) } } diff --git a/EhPanda/View/Setting/EhPandaView.swift b/EhPanda/View/Setting/EhPandaView.swift index 60be92ff..ea41e81e 100644 --- a/EhPanda/View/Setting/EhPandaView.swift +++ b/EhPanda/View/Setting/EhPandaView.swift @@ -55,6 +55,10 @@ struct EhPandaView: View, StoreAccessor { url: "https://github.com/tid-kijyun/Kanna", text: "Kanna" ), + Info( + url: "https://github.com/rebeloper/AlertKit", + text: "AlertKit" + ), Info( url: "https://github.com/onevcat/Kingfisher", text: "Kingfisher" diff --git a/EhPanda/View/Tools/AlertView.swift b/EhPanda/View/Tools/AlertView.swift index f7dd5951..b2b29520 100644 --- a/EhPanda/View/Tools/AlertView.swift +++ b/EhPanda/View/Tools/AlertView.swift @@ -113,3 +113,31 @@ struct GenericRetryView: View { .frame(maxWidth: windowW * 0.8) } } + +struct PageJumpView: View { + @Environment(\.colorScheme) private var colorScheme + @Binding private var inputText: String + private let pageNumber: PageNumber + + init(inputText: Binding, pageNumber: PageNumber) { + _inputText = inputText + self.pageNumber = pageNumber + } + + var body: some View { + VStack { + Text("Jump page").bold() + HStack { + let opacity = colorScheme == .light ? 0.15 : 0.1 + TextField(inputText, text: $inputText) + .multilineTextAlignment(.center).keyboardType(.numberPad) + .padding(.horizontal, 10).padding(.vertical, 5) + .background(Color.gray.opacity(opacity)) + .cornerRadius(5).frame(width: 75) + Text("-") + Text("\(pageNumber.maximum + 1)") + } + .lineLimit(1) + } + } +} From 42f5feb789d0d385fcc2e04520500d3d8f284c43 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, 26 Sep 2021 01:28:52 +0800 Subject: [PATCH 8/8] Optimize page jump feature --- EhPanda/App/Utility.swift | 1 + EhPanda/View/Detail/AssociatedView.swift | 30 ++++++++++++++++-------- EhPanda/View/Home/HomeView.swift | 30 ++++++++++++++++-------- EhPanda/View/Tools/AlertView.swift | 8 ++++++- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/EhPanda/App/Utility.swift b/EhPanda/App/Utility.swift index c64cf448..687b78d6 100644 --- a/EhPanda/App/Utility.swift +++ b/EhPanda/App/Utility.swift @@ -265,6 +265,7 @@ func handleIncomingURL( completion(false, url, nil, nil) } +@available(*, deprecated, message: "Use @FocusState instead.") func hideKeyboard() { UIApplication.shared.sendAction( #selector(UIResponder.resignFirstResponder), diff --git a/EhPanda/View/Detail/AssociatedView.swift b/EhPanda/View/Detail/AssociatedView.swift index be61d444..75406b23 100644 --- a/EhPanda/View/Detail/AssociatedView.swift +++ b/EhPanda/View/Detail/AssociatedView.swift @@ -23,6 +23,7 @@ struct AssociatedView: View, StoreAccessor { @State private var pageNumber = PageNumber() @State private var alertInput = "" + @FocusState private var isAlertFocused: Bool @StateObject private var alertManager = CustomAlertManager() init(keyword: String) { @@ -68,15 +69,23 @@ struct AssociatedView: View, StoreAccessor { } } } - .customAlert(manager: alertManager, widthFactor: isPadWidth ? 0.5 : 1.0, content: { - PageJumpView(inputText: $alertInput, pageNumber: pageNumber) - }, buttons: [ - .regular { - Text("Confirm") - } action: { - performJumpPage() - } - ]) + .customAlert( + manager: alertManager, + widthFactor: isPadWidth ? 0.5 : 1.0, + content: { + PageJumpView( + inputText: $alertInput, + isFocused: $isAlertFocused, + pageNumber: pageNumber + ) + }, buttons: [ + .regular { + Text("Confirm") + } action: { + performJumpPage() + } + ] + ) .navigationBarTitle(title) .onSubmit(of: .search, fetchAssociatedItems) .onAppear(perform: fetchAssociatedItemsIfNeeded) @@ -105,7 +114,7 @@ private extension AssociatedView { } } func onAlertVisibilityChanged(_: Bool) { - hideKeyboard() + isAlertFocused = false } func onPageNumberChanged(pageNumber: PageNumber) { alertInput = String(pageNumber.current + 1) @@ -255,6 +264,7 @@ private extension AssociatedView { } func toggleJumpPage() { alertManager.show() + isAlertFocused = true } func performJumpPage() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { diff --git a/EhPanda/View/Home/HomeView.swift b/EhPanda/View/Home/HomeView.swift index 3b4d6b28..f8b5e2e2 100644 --- a/EhPanda/View/Home/HomeView.swift +++ b/EhPanda/View/Home/HomeView.swift @@ -22,6 +22,7 @@ struct HomeView: View, StoreAccessor { @State private var hudConfig = TTProgressHUDConfig() @State private var alertInput = "" + @FocusState private var isAlertFocused: Bool @StateObject private var alertManager = CustomAlertManager() // MARK: HomeView @@ -112,15 +113,23 @@ struct HomeView: View, StoreAccessor { .onOpenURL(perform: onOpen) .navigationViewStyle(.stack) .onAppear(perform: onStartTasks) - .customAlert(manager: alertManager, widthFactor: isPadWidth ? 0.5 : 1.0, content: { - PageJumpView(inputText: $alertInput, pageNumber: currentListTypePageNumber) - }, buttons: [ - .regular { - Text("Confirm") - } action: { - performJumpPage() - } - ]) + .customAlert( + manager: alertManager, + widthFactor: isPadWidth ? 0.5 : 1.0, + content: { + PageJumpView( + inputText: $alertInput, + isFocused: $isAlertFocused, + pageNumber: currentListTypePageNumber + ) + }, buttons: [ + .regular { + Text("Confirm") + } action: { + performJumpPage() + } + ] + ) .sheet(item: environmentBinding.homeViewSheetState) { item in Group { switch item { @@ -435,7 +444,7 @@ private extension HomeView { store.dispatch(.updateSearchKeyword(text: word)) } func onAlertVisibilityChanged(_: Bool) { - hideKeyboard() + isAlertFocused = false } func onPageNumberChanged(pageNumber: PageNumber) { alertInput = String(pageNumber.current + 1) @@ -620,6 +629,7 @@ private extension HomeView { } func toggleJumpPage() { alertManager.show() + isAlertFocused = true } func performJumpPage() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { diff --git a/EhPanda/View/Tools/AlertView.swift b/EhPanda/View/Tools/AlertView.swift index b2b29520..0cebd15b 100644 --- a/EhPanda/View/Tools/AlertView.swift +++ b/EhPanda/View/Tools/AlertView.swift @@ -117,10 +117,15 @@ struct GenericRetryView: View { struct PageJumpView: View { @Environment(\.colorScheme) private var colorScheme @Binding private var inputText: String + private var isFocused: FocusState.Binding private let pageNumber: PageNumber - init(inputText: Binding, pageNumber: PageNumber) { + init(inputText: Binding, + isFocused: FocusState.Binding, + pageNumber: PageNumber + ) { _inputText = inputText + self.isFocused = isFocused self.pageNumber = pageNumber } @@ -134,6 +139,7 @@ struct PageJumpView: View { .padding(.horizontal, 10).padding(.vertical, 5) .background(Color.gray.opacity(opacity)) .cornerRadius(5).frame(width: 75) + .focused(isFocused.projectedValue) Text("-") Text("\(pageNumber.maximum + 1)") }