From 3798437bf167c2ebea827e6295a0781f5262ae29 Mon Sep 17 00:00:00 2001 From: 82Flex <82flex@gmail.com> Date: Tue, 23 Jul 2024 14:28:46 +0800 Subject: [PATCH] bump version Signed-off-by: 82Flex <82flex@gmail.com> --- TrollFools.xcodeproj/project.pbxproj | 4 +- TrollFools/AppListView.swift | 4 +- TrollFools/EjectListView.swift | 109 +++++++++++++++------------ TrollFools/OptionView.swift | 1 - 4 files changed, 65 insertions(+), 53 deletions(-) diff --git a/TrollFools.xcodeproj/project.pbxproj b/TrollFools.xcodeproj/project.pbxproj index b78797b..58df024 100644 --- a/TrollFools.xcodeproj/project.pbxproj +++ b/TrollFools.xcodeproj/project.pbxproj @@ -439,7 +439,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = GXZ23M5TP2; ENABLE_PREVIEWS = NO; GENERATE_INFOPLIST_FILE = YES; @@ -481,7 +481,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = GXZ23M5TP2; ENABLE_PREVIEWS = NO; GENERATE_INFOPLIST_FILE = YES; diff --git a/TrollFools/AppListView.swift b/TrollFools/AppListView.swift index e78bc57..ae96fa5 100644 --- a/TrollFools/AppListView.swift +++ b/TrollFools/AppListView.swift @@ -70,9 +70,9 @@ final class AppListModel: ObservableObject { filter.$searchKeyword .combineLatest(filter.$showPatchedOnly) .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true) - .sink { _ in + .sink { [weak self] _ in withAnimation { - self.performFilter() + self?.performFilter() } } .store(in: &cancellables) diff --git a/TrollFools/EjectListView.swift b/TrollFools/EjectListView.swift index 58a6b51..a62efd8 100644 --- a/TrollFools/EjectListView.swift +++ b/TrollFools/EjectListView.swift @@ -6,6 +6,7 @@ // import CocoaLumberjackSwift +import Combine import SwiftUI private let gDateFormatter: DateFormatter = { @@ -30,13 +31,13 @@ struct InjectedPlugIn: Identifiable { struct PlugInCell: View { let plugIn: InjectedPlugIn - @EnvironmentObject var searchOptions: FilterOptions + @EnvironmentObject var filter: FilterOptions @available(iOS 15.0, *) var highlightedName: AttributedString { let name = plugIn.url.lastPathComponent var attributedString = AttributedString(name) - if let range = attributedString.range(of: searchOptions.searchKeyword, options: [.caseInsensitive, .diacriticInsensitive]) { + if let range = attributedString.range(of: filter.searchKeyword, options: [.caseInsensitive, .diacriticInsensitive]) { attributedString[range].foregroundColor = .accentColor } return attributedString @@ -80,31 +81,61 @@ struct PlugInCell: View { } } -struct EjectListView: View { +final class EjectListModel: ObservableObject { let app: App + private var _injectedPlugIns: [InjectedPlugIn] = [] + + @Published var filter = FilterOptions() + @Published var filteredPlugIns: [InjectedPlugIn] = [] + + private var cancellables = Set() init(_ app: App) { self.app = app + reload() + + filter.$searchKeyword + .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true) + .sink { [weak self] _ in + withAnimation { + self?.performFilter() + } + } + .store(in: &cancellables) } - @State var injectedPlugIns: [InjectedPlugIn] = [] - @State var isErrorOccurred: Bool = false - @State var errorMessage: String = "" + func reload() { + self._injectedPlugIns = Injector.injectedPlugInURLs(app.url) + .map { InjectedPlugIn(url: $0) } + performFilter() + } - @State var searchResults: [InjectedPlugIn] = [] - @StateObject var searchOptions = FilterOptions() + func performFilter() { + var filteredPlugIns = _injectedPlugIns - @State var isDeletingAll = false - @StateObject var viewControllerHost = ViewControllerHost() + if !filter.searchKeyword.isEmpty { + filteredPlugIns = filteredPlugIns.filter { + $0.url.lastPathComponent.localizedCaseInsensitiveContains(filter.searchKeyword) + } + } - var isSearching: Bool { - return !searchOptions.searchKeyword.isEmpty + self.filteredPlugIns = filteredPlugIns } +} + +struct EjectListView: View { + @StateObject var vm: EjectListModel - var filteredPlugIns: [InjectedPlugIn] { - isSearching ? searchResults : injectedPlugIns + init(_ app: App) { + _vm = StateObject(wrappedValue: EjectListModel(app)) } + @State var isErrorOccurred: Bool = false + @State var errorMessage: String = "" + + @State var isDeletingAll = false + @StateObject var viewControllerHost = ViewControllerHost() + var deleteAllButtonLabel: some View { HStack { Label(NSLocalizedString("Eject All", comment: ""), systemImage: "eject") @@ -135,31 +166,31 @@ struct EjectListView: View { var ejectList: some View { List { Section { - ForEach(filteredPlugIns) { plugin in + ForEach(vm.filteredPlugIns) { plugin in if #available(iOS 16.0, *) { PlugInCell(plugIn: plugin) - .environmentObject(searchOptions) + .environmentObject(vm.filter) } else { PlugInCell(plugIn: plugin) - .environmentObject(searchOptions) + .environmentObject(vm.filter) .padding(.vertical, 4) } } .onDelete(perform: delete) } header: { - Text(filteredPlugIns.isEmpty + Text(vm.filteredPlugIns.isEmpty ? NSLocalizedString("No Injected Plug-Ins", comment: "") : NSLocalizedString("Injected Plug-Ins", comment: "")) .font(.footnote) } - if !isSearching && !filteredPlugIns.isEmpty { + if !vm.filter.isSearching && !vm.filteredPlugIns.isEmpty { Section { deleteAllButton .disabled(isDeletingAll) .foregroundColor(isDeletingAll ? .secondary : .red) } footer: { - if app.isFromTroll { + if vm.app.isFromTroll { Text(NSLocalizedString("Some plug-ins were not injected by TrollFools, please eject them with caution.", comment: "")) .font(.footnote) } @@ -173,13 +204,10 @@ struct EjectListView: View { } .listStyle(.insetGrouped) .navigationTitle(NSLocalizedString("Plug-Ins", comment: "")) - .navigationBarTitleDisplayMode(.inline) + .animation(.easeOut, value: vm.filter.isSearching) .onViewWillAppear { viewController in viewControllerHost.viewController = viewController } - .onAppear { - reloadPlugIns() - } } var body: some View { @@ -187,39 +215,30 @@ struct EjectListView: View { ejectList .refreshable { withAnimation { - reloadPlugIns() + vm.reload() } } .searchable( - text: $searchOptions.searchKeyword, + text: $vm.filter.searchKeyword, placement: .automatic, prompt: NSLocalizedString("Search…", comment: "") ) .textInputAutocapitalization(.never) - .onChange(of: searchOptions.searchKeyword) { keyword in - fetchSearchResults(for: keyword) - } } else { // Fallback on earlier versions ejectList } } - func reloadPlugIns() { - searchOptions.reset() - injectedPlugIns = Injector.injectedPlugInURLs(app.url) - .map { InjectedPlugIn(url: $0) } - } - func delete(at offsets: IndexSet) { do { - let plugInsToRemove = offsets.map { filteredPlugIns[$0] } + let plugInsToRemove = offsets.map { vm.filteredPlugIns[$0] } let plugInURLsToRemove = plugInsToRemove.map { $0.url } - let injector = try Injector(bundleURL: app.url, teamID: app.teamID) + let injector = try Injector(bundleURL: vm.app.url, teamID: vm.app.teamID) try injector.eject(plugInURLsToRemove) - app.reloadInjectedStatus() - reloadPlugIns() + vm.app.reloadInjectedStatus() + vm.reload() } catch { DDLogError("\(error)") @@ -230,7 +249,7 @@ struct EjectListView: View { func deleteAll() { do { - let injector = try Injector(bundleURL: app.url, teamID: app.teamID) + let injector = try Injector(bundleURL: vm.app.url, teamID: vm.app.teamID) let view = viewControllerHost.viewController? .navigationController?.view @@ -245,8 +264,8 @@ struct EjectListView: View { defer { DispatchQueue.main.async { withAnimation { - app.reloadInjectedStatus() - reloadPlugIns() + vm.app.reloadInjectedStatus() + vm.reload() isDeletingAll = false } @@ -274,10 +293,4 @@ struct EjectListView: View { isErrorOccurred = true } } - - func fetchSearchResults(for query: String) { - searchResults = injectedPlugIns.filter { plugin in - plugin.url.lastPathComponent.localizedCaseInsensitiveContains(query) - } - } } diff --git a/TrollFools/OptionView.swift b/TrollFools/OptionView.swift index a11dc31..65a5310 100644 --- a/TrollFools/OptionView.swift +++ b/TrollFools/OptionView.swift @@ -102,7 +102,6 @@ struct OptionView: View { } .padding() .navigationTitle(app.name) - .navigationBarTitleDisplayMode(.inline) .fileImporter( isPresented: $isImporterPresented, allowedContentTypes: [