From fba250d5fb5e761da9fb4ae01f76407bb0c3ff37 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Wed, 27 Nov 2024 16:17:43 -0500 Subject: [PATCH 01/16] =?UTF-8?q?Remove=20some=20broken=20cores=20from=20?= =?UTF-8?q?=E2=80=9Ccatalyst=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Joseph Mattiello --- Provenance.xcodeproj/project.pbxproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Provenance.xcodeproj/project.pbxproj b/Provenance.xcodeproj/project.pbxproj index 2032ce61e6..075164498a 100644 --- a/Provenance.xcodeproj/project.pbxproj +++ b/Provenance.xcodeproj/project.pbxproj @@ -816,16 +816,12 @@ B3E2AA222CB6306600E2636D /* PVYabause.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA202CB6306600E2636D /* PVYabause.framework */; platformFilters = (maccatalyst, macos, ); settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B3E2AA252CB6328100E2636D /* PVVecX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA242CB6328100E2636D /* PVVecX.framework */; }; B3E2AA262CB6328100E2636D /* PVVecX.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA242CB6328100E2636D /* PVVecX.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - B3E2AA2C2CB6340F00E2636D /* PVPCSXRearmed.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA2B2CB6340F00E2636D /* PVPCSXRearmed.framework */; platformFilter = maccatalyst; }; - B3E2AA2D2CB6341000E2636D /* PVPCSXRearmed.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA2B2CB6340F00E2636D /* PVPCSXRearmed.framework */; platformFilter = maccatalyst; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B3E2AA302CB6346900E2636D /* PVOpera.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA2F2CB6346900E2636D /* PVOpera.framework */; }; B3E2AA312CB6346900E2636D /* PVOpera.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA2F2CB6346900E2636D /* PVOpera.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B3E2AA382CB635BB00E2636D /* PVGME.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA372CB635BB00E2636D /* PVGME.framework */; }; B3E2AA392CB635BB00E2636D /* PVGME.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA372CB635BB00E2636D /* PVGME.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B3E2AA3E2CB6362C00E2636D /* PVGearcoleco.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA3D2CB6362C00E2636D /* PVGearcoleco.framework */; }; B3E2AA3F2CB6362D00E2636D /* PVGearcoleco.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA3D2CB6362C00E2636D /* PVGearcoleco.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - B3E2AA412CB6369800E2636D /* PVFlycast.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA402CB6369800E2636D /* PVFlycast.framework */; platformFilters = (maccatalyst, macos, ); }; - B3E2AA422CB6369800E2636D /* PVFlycast.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA402CB6369800E2636D /* PVFlycast.framework */; platformFilters = (maccatalyst, macos, ); settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B3E2AA482CB6381400E2636D /* PVEP128Emu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA472CB6381400E2636D /* PVEP128Emu.framework */; }; B3E2AA492CB6381500E2636D /* PVEP128Emu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA472CB6381400E2636D /* PVEP128Emu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B3E2AA4E2CB638F400E2636D /* PVDesmume2015.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3E2AA4D2CB638F400E2636D /* PVDesmume2015.framework */; }; @@ -1555,12 +1551,10 @@ B38FB4A92CBEF6B0006786C6 /* libavcodec.xcframework in Embed Frameworks */, B33BD1C92C6A6261001E933A /* PVStella-Dynamic in Embed Frameworks */, B386DE862C8974DA0021A7BA /* PVFreeDO-Dynamic in Embed Frameworks */, - B3E2AA422CB6369800E2636D /* PVFlycast.framework in Embed Frameworks */, B3E2AA262CB6328100E2636D /* PVVecX.framework in Embed Frameworks */, B3A186862CBCBAC800BB1FFE /* PVMupen64PlusBridge.framework in Embed Frameworks */, B318E28D2CAB512F00D0E599 /* PVMupen64PlusVideoRice.framework in Embed Frameworks */, B38FB4AF2CBEF6B1006786C6 /* libswresample.xcframework in Embed Frameworks */, - B3E2AA2D2CB6341000E2636D /* PVPCSXRearmed.framework in Embed Frameworks */, B33BD1C62C6A622A001E933A /* PVTGBDual-Dynamic in Embed Frameworks */, B33BD1C02C6A6212001E933A /* PVPicoDrive-Dynamic in Embed Frameworks */, B38FB4A22CBEF57C006786C6 /* PVCoreBridgeRetro.framework in Embed Frameworks */, @@ -2942,7 +2936,6 @@ B318E29A2CAB586800D0E599 /* snes9x.framework in Frameworks */, B33BD1BF2C6A6212001E933A /* PVPicoDrive-Dynamic in Frameworks */, B3952F582C698F54000B0308 /* Defaults in Frameworks */, - B3E2AA412CB6369800E2636D /* PVFlycast.framework in Frameworks */, B3952F562C697A02000B0308 /* PVUI in Frameworks */, B318E27D2CAB4C4200D0E599 /* PVSNES.framework in Frameworks */, B3EF26EA2BE7F8BD0066D4B6 /* libz.tbd in Frameworks */, @@ -2975,7 +2968,6 @@ B394CD7C2BDEE53A006B63E8 /* CoreServices.framework in Frameworks */, B394CD872BDEE53A006B63E8 /* AVFoundation.framework in Frameworks */, B394CD892BDEE53A006B63E8 /* CFNetwork.framework in Frameworks */, - B3E2AA2C2CB6340F00E2636D /* PVPCSXRearmed.framework in Frameworks */, B394CD8E2BDEE53A006B63E8 /* Security.framework in Frameworks */, B37228CA2CCDC7A700E6F627 /* FreemiumKit in Frameworks */, B318E2802CAB4C6400D0E599 /* PVFCEU.framework in Frameworks */, From 19a5e35226ea2b4e8a8fcdfd83a48e7a243020f6 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Wed, 27 Nov 2024 17:47:50 -0500 Subject: [PATCH 02/16] UITesting app can show imports queue Signed-off-by: Joseph Mattiello --- .../PVSwiftUI/Imports/ImportStatusView.swift | 28 ++++-- UITesting/UITesting.code-workspace | 2 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../MockImportStatusDelegate.swift | 36 ++++++++ UITesting/UITesting/UITestingApp.swift | 92 ++++++++++++------- 5 files changed, 121 insertions(+), 39 deletions(-) create mode 100644 UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift diff --git a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift index f308bab315..d5c41f303a 100644 --- a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift +++ b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift @@ -33,16 +33,25 @@ func iconNameForFileType(_ type: FileType) -> String { } } -struct ImportStatusView: View { - @ObservedObject var updatesController: PVGameLibraryUpdatesController - var gameImporter:GameImporter - weak var delegate:ImportStatusDelegate! +public struct ImportStatusView: View { + @ObservedObject + public var updatesController: PVGameLibraryUpdatesController + public var gameImporter:GameImporter + public weak var delegate:ImportStatusDelegate! + public var dismissAction: (() -> Void)? = nil + + public init(updatesController: PVGameLibraryUpdatesController, gameImporter:GameImporter, delegate:ImportStatusDelegate, dismissAction: (() -> Void)? = nil) { + self.updatesController = updatesController + self.gameImporter = gameImporter + self.delegate = delegate + self.dismissAction = dismissAction + } private func deleteItems(at offsets: IndexSet) { gameImporter.removeImports(at: offsets) } - var body: some View { + public var body: some View { WithPerceptionTracking { NavigationView { List { @@ -83,5 +92,12 @@ struct ImportStatusView: View { } //#Preview { -// +// let gameImporter = AppState.shared.gameImporter ?? GameImporter.shared +// let pvgamelibraryUpdatesController = PVGameLibraryUpdatesController(gameImporter: gameImporter) +// let importDelegate = MockImportStatusDelegate() +// +// ImportStatusView( +// updatesController: pvgamelibraryUpdatesController, +// gameImporter: gameImporter, +// delegate: importDelegate) //} diff --git a/UITesting/UITesting.code-workspace b/UITesting/UITesting.code-workspace index 57097327f4..16dc15c8c1 100644 --- a/UITesting/UITesting.code-workspace +++ b/UITesting/UITesting.code-workspace @@ -1,7 +1,7 @@ { "folders": [ { - "path": "." + "path": "UITesting" } ], "settings": {} diff --git a/UITesting/UITesting.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/UITesting/UITesting.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2eb8e28e29..cf62b60ee1 100644 --- a/UITesting/UITesting.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/UITesting/UITesting.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -241,7 +241,7 @@ "location" : "https://github.com/pointfreeco/swift-perception.git", "state" : { "branch" : "main", - "revision" : "dccdf5aed1db969afa11d6fbd36b96a4932ebe8c" + "revision" : "8d52279b9809ef27eabe7d5420f03734528f19da" } }, { diff --git a/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift b/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift new file mode 100644 index 0000000000..7e073ca38f --- /dev/null +++ b/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift @@ -0,0 +1,36 @@ +// +// MockImportStatusDelegate.swift +// UITesting +// +// Created by Joseph Mattiello on 11/27/24. +// + +import PVSwiftUI + +class MockImportStatusDriverData: ObservableObject { + @MainActor + let gameImporter = AppState.shared.gameImporter ?? GameImporter.shared + @MainActor + let pvgamelibraryUpdatesController: PVGameLibraryUpdatesController + + var isPresent: Bool = false + + @MainActor + init() { + pvgamelibraryUpdatesController = .init(gameImporter: gameImporter) + } +} + +extension MockImportStatusDriverData: ImportStatusDelegate { + func dismissAction() { + + } + + func addImportsAction() { + + } + + func forceImportsAction() { + + } +} diff --git a/UITesting/UITesting/UITestingApp.swift b/UITesting/UITesting/UITestingApp.swift index 1c19fea561..b6eadc476a 100644 --- a/UITesting/UITesting/UITestingApp.swift +++ b/UITesting/UITesting/UITestingApp.swift @@ -10,46 +10,56 @@ import PVSwiftUI import PVThemes import SwiftUI import UIKit +import Perception + #if canImport(FreemiumKit) import FreemiumKit #endif @main struct UITestingApp: App { - @State private var showingRealmSheet = true + @State private var showingRealmSheet = false @State private var showingMockSheet = false @State private var showingSettings = false - + @State private var showImportStatus = false + + @StateObject + private var mockImportStatusDriverData = MockImportStatusDriverData() + var body: some Scene { WindowGroup { ZStack { - Color.black.ignoresSafeArea() - + Color.secondary.ignoresSafeArea() + VStack(spacing: 20) { Button("Show Realm Driver") { showingRealmSheet = true } .buttonStyle(.borderedProminent) - + Button("Show Mock Driver") { showingMockSheet = true } .buttonStyle(.borderedProminent) - Button("Show Settings") { showingSettings = true } .buttonStyle(.borderedProminent) + + Button("Show Import Queue") { + showImportStatus = true + } + .buttonStyle(.borderedProminent) } } .sheet(isPresented: $showingRealmSheet) { let testRealm = try! RealmSaveStateTestFactory.createInMemoryRealm() let mockDriver = try! RealmSaveStateDriver(realm: testRealm) - + /// Get the first game from realm for the view model let game = testRealm.objects(PVGame.self).first! - + /// Create view model with game data let viewModel = ContinuesMagementViewModel( driver: mockDriver, @@ -57,14 +67,11 @@ struct UITestingApp: App { systemTitle: "Game Boy", numberOfSaves: game.saveStates.count ) - + ContinuesMagementView(viewModel: viewModel) .onAppear { /// Load initial states through the publisher mockDriver.loadSaveStates(forGameId: "1") - - let theme = CGAThemes.purple - ThemeManager.shared.setCurrentPalette(theme.palette) } .presentationBackground(.clear) } @@ -79,11 +86,9 @@ struct UITestingApp: App { numberOfSaves: mockDriver.getAllSaveStates().count, gameUIImage: mockDriver.gameUIImage ) - + ContinuesMagementView(viewModel: viewModel) .onAppear { - let theme = CGAThemes.purple - ThemeManager.shared.setCurrentPalette(theme.palette) } .presentationBackground(.clear) } @@ -91,28 +96,36 @@ struct UITestingApp: App { let gameImporter = GameImporter.shared let pvgamelibraryUpdatesController = PVGameLibraryUpdatesController(gameImporter: gameImporter) let menuDelegate = MockPVMenuDelegate() - + PVSettingsView( conflictsController: pvgamelibraryUpdatesController, menuDelegate: menuDelegate) { - + print("PVSettingsView Closed") + } + } + .sheet(isPresented: $showImportStatus) { + ImportStatusView( + updatesController: mockImportStatusDriverData.pvgamelibraryUpdatesController, + gameImporter: mockImportStatusDriverData.gameImporter, + delegate: mockImportStatusDriverData) { + print("Import Status View Closed") } } .onAppear { - #if canImport(FreemiumKit) - FreemiumKit.shared.overrideForDebug(purchasedTier: 1) - #endif +#if canImport(FreemiumKit) + FreemiumKit.shared.overrideForDebug(purchasedTier: 1) +#endif } } #if canImport(FreemiumKit) - .environmentObject(FreemiumKit.shared) + .environmentObject(FreemiumKit.shared) #endif } } class MockPVMenuDelegate: PVMenuDelegate { func didTapImports() { - + } func didTapSettings() { @@ -141,38 +154,55 @@ class MockPVMenuDelegate: PVMenuDelegate { } struct MainView_Previews: PreviewProvider { - @State private var showingSettings = false - + @State static private var showSaveStatesMock = false + @State static private var showingSettings = false + @State static private var showImportQueue = false + @State static private var mockImportStatusDriverData = MockImportStatusDriverData() + static var previews: some View { ZStack { Color.black.ignoresSafeArea() - + VStack(spacing: 20) { Button("Show Realm Driver") { } .buttonStyle(.borderedProminent) - + Button("Show Mock Driver") { } .buttonStyle(.borderedProminent) Button("Show Settings") { } .buttonStyle(.borderedProminent) + + Button("Show Import Queue") { + showImportQueue = true + } + .buttonStyle(.borderedProminent) } } - .sheet(isPresented: .constant(false)) { + .sheet(isPresented: $showingSettings) { let gameImporter = GameImporter.shared let pvgamelibraryUpdatesController = PVGameLibraryUpdatesController(gameImporter: gameImporter) let menuDelegate = MockPVMenuDelegate() - + PVSettingsView( conflictsController: pvgamelibraryUpdatesController, menuDelegate: menuDelegate) { } } + .sheet(isPresented: .init( + get: { mockImportStatusDriverData.isPresent }, + set: { mockImportStatusDriverData.isPresent = $0 } + )) { + ImportStatusView( + updatesController: mockImportStatusDriverData.pvgamelibraryUpdatesController, + gameImporter: mockImportStatusDriverData.gameImporter, + delegate: mockImportStatusDriverData) + } .onAppear { - #if canImport(FreemiumKit) - FreemiumKit.shared.overrideForDebug(purchasedTier: 1) - #endif +#if canImport(FreemiumKit) + FreemiumKit.shared.overrideForDebug(purchasedTier: 1) +#endif } } } From 472aea795ad216c26c96d9bbdfefdcecdcdeae90 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Wed, 27 Nov 2024 17:48:38 -0500 Subject: [PATCH 03/16] remove comment Signed-off-by: Joseph Mattiello --- PVUI/Sources/PVSwiftUI/Settings/SettingsSwiftUI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PVUI/Sources/PVSwiftUI/Settings/SettingsSwiftUI.swift b/PVUI/Sources/PVSwiftUI/Settings/SettingsSwiftUI.swift index df4fc36ee7..d8dcaab710 100644 --- a/PVUI/Sources/PVSwiftUI/Settings/SettingsSwiftUI.swift +++ b/PVUI/Sources/PVSwiftUI/Settings/SettingsSwiftUI.swift @@ -30,7 +30,7 @@ public struct PVSettingsView: View { @StateObject private var viewModel: PVSettingsViewModel @ObservedObject private var themeManager = ThemeManager.shared - var dismissAction: () -> Void // Add this + var dismissAction: () -> Void weak var menuDelegate: PVMenuDelegate! @ObservedObject var conflictsController: PVGameLibraryUpdatesController From 57dc1cb94b0edb8ec3e6a6b7ea1f04365c4eded9 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Wed, 27 Nov 2024 17:54:02 -0500 Subject: [PATCH 04/16] continuesview can be shown half way first Signed-off-by: Joseph Mattiello --- .../ContinuesManagementView/ContinuesMagementView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PVUI/Sources/PVSwiftUI/ContinuesManagementView/ContinuesMagementView.swift b/PVUI/Sources/PVSwiftUI/ContinuesManagementView/ContinuesMagementView.swift index fddbe759f3..c82bd571f7 100644 --- a/PVUI/Sources/PVSwiftUI/ContinuesManagementView/ContinuesMagementView.swift +++ b/PVUI/Sources/PVSwiftUI/ContinuesManagementView/ContinuesMagementView.swift @@ -398,6 +398,8 @@ public struct ContinuesMagementView: View { .onAppear { viewModel.subscribeToDriverPublisher() } + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.visible) } } From b8e3c623914d91fefd8041a9f8e6aaab38449467 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Wed, 27 Nov 2024 18:31:50 -0500 Subject: [PATCH 05/16] add beginings of mock importer Signed-off-by: Joseph Mattiello --- .../MockGameImporter.swift | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift diff --git a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift new file mode 100644 index 0000000000..c6e9de4c6b --- /dev/null +++ b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift @@ -0,0 +1,45 @@ +// +// MockGameImporter.swift +// UITesting +// +// Created by Joseph Mattiello on 11/27/24. +// + +import PVSwiftUI +import SwiftUI + +class MockGameImporter: GameImporting { + func initSystems() async { + + } + + var importStatus: String { + "" + } + + var importQueue: [ImportQueueItem] = [] + + var processingState: ProcessingState = .idle + + func addImport(_ item: ImportQueueItem) { + + } + func addImports(forPaths paths: [URL]) { + + } + + func removeImports(at offsets: IndexSet) { + + } + func startProcessing() { + + } + + func sortImportQueueItems(_ importQueueItems: [ImportQueueItem]) -> [ImportQueueItem] { + importQueueItems.sorted(by: { $0.url.lastPathComponent < $1.url.lastPathComponent }) + } + + func importQueueContainsDuplicate(_ queue: [ImportQueueItem], ofItem queueItem: ImportQueueItem) -> Bool { + queue.contains(where: { $0.url == queueItem.url }) + } +} From c2623378f9061eb8b7a77a4082a73e5cd6fa6a58 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 02:32:27 -0500 Subject: [PATCH 06/16] Start of generic support for importer Signed-off-by: Joseph Mattiello --- .../Importer/Models/ImportQueueItem.swift | 38 ++++++++++++++----- .../GameImporter/ArtworkImporter.swift | 20 ++++++---- .../Services/GameImporter/CDFileHandler.swift | 1 + .../Services/GameImporter/GameImporter.swift | 35 +++++++++++++---- .../GameImporterDatabaseService.swift | 35 ++++++++++++----- .../GameImporterFileService.swift | 3 +- .../GameImporterSystemsService.swift | 30 +++++++++------ .../Entities/PVLibraryEntry.swift | 3 +- .../Protocols/SystemProtocol.swift | 14 ++++++- 9 files changed, 131 insertions(+), 48 deletions(-) diff --git a/PVLibrary/Sources/PVLibrary/Importer/Models/ImportQueueItem.swift b/PVLibrary/Sources/PVLibrary/Importer/Models/ImportQueueItem.swift index 67ef108102..a88cbdfce3 100644 --- a/PVLibrary/Sources/PVLibrary/Importer/Models/ImportQueueItem.swift +++ b/PVLibrary/Sources/PVLibrary/Importer/Models/ImportQueueItem.swift @@ -10,13 +10,17 @@ import PVPrimitives import Perception // Enum to define the possible statuses of each import -public enum ImportStatus: String { - case queued +public enum ImportStatus: Int, CustomStringConvertible { + case conflict // Indicates additional action needed by user after successful import + case partial //indicates the item is waiting for associated files before it could be processed case processing - case success + + case queued + case failure - case conflict // Indicates additional action needed by user after successful import + + case success public var description: String { switch self { @@ -55,16 +59,20 @@ public enum ProcessingState { // ImportItem model to hold each file's metadata and progress @Perceptible public class ImportQueueItem: Identifiable, ObservableObject { + + // TODO: Make this more generic with AnySystem, some System? + public typealias System = PVSystem //AnySystem + public let id = UUID() public var url: URL public var fileType: FileType - public var systems: [PVSystem] // Can be set to the specific system type - public var userChosenSystem: PVSystem? + public var systems: [System] // Can be set to the specific system type + public var userChosenSystem: (System)? public var destinationUrl: URL? public var errorValue: String? //this is used when a single import has child items - e.g., m3u, cue, directory - public var childQueueItems:[ImportQueueItem] + public var childQueueItems: [ImportQueueItem] // Observable status for individual imports public var status: ImportStatus = .queued @@ -94,7 +102,7 @@ public class ImportQueueItem: Identifiable, ObservableObject { var md5: String? } - public func targetSystem() -> PVSystem? { + public func targetSystem() -> (any SystemProtocol)? { guard !systems.isEmpty else { return nil } @@ -105,7 +113,7 @@ public class ImportQueueItem: Identifiable, ObservableObject { if let chosenSystem = userChosenSystem { - var target:PVSystem? = nil + var target:(any SystemProtocol)? = nil for system in systems { if (chosenSystem.identifier == system.identifier) { @@ -139,3 +147,15 @@ public class ImportQueueItem: Identifiable, ObservableObject { return current } } + +extension ImportQueueItem: Equatable { + public static func == (lhs: ImportQueueItem, rhs: ImportQueueItem) -> Bool { + return lhs.url == rhs.url + } +} + +extension ImportQueueItem: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(url) + } +} diff --git a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/ArtworkImporter.swift b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/ArtworkImporter.swift index 7bebc00104..1227795216 100644 --- a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/ArtworkImporter.swift +++ b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/ArtworkImporter.swift @@ -5,24 +5,30 @@ // Created by David Proskin on 11/3/24. // +import PVPrimitives + protocol ArtworkImporting { - func setSystemsService(_ systemsService:GameImporterSystemsServicing) - func importArtworkItem(_ queueItem: ImportQueueItem) async -> PVGame? + // TODO: Make me more generic +// associatedtype MyGameImporterSystemsService: GameImporterSystemsServicing + typealias MyGameImporterSystemsService = GameImporterSystemsServicing + + func setSystemsService(_ systemsService: MyGameImporterSystemsService) + func importArtworkItem(_ queueItem: ImportQueueItem) async -> MyGameImporterSystemsService.GameType? } class ArtworkImporter : ArtworkImporting { - - private var gameImporterSystemsService:GameImporterSystemsServicing? + + private var gameImporterSystemsService: (any GameImporterSystemsServicing)? init() { } - func setSystemsService(_ systemsService:GameImporterSystemsServicing) { + func setSystemsService(_ systemsService: MyGameImporterSystemsService) { gameImporterSystemsService = systemsService } - func importArtworkItem(_ queueItem: ImportQueueItem) async -> PVGame? { + func importArtworkItem(_ queueItem: ImportQueueItem) async -> MyGameImporterSystemsService.GameType? { guard queueItem.fileType == .artwork else { return nil } @@ -108,7 +114,7 @@ class ArtworkImporter : ArtworkImporting { } catch { ELOG("Couldn't update game \(onlyMatch.title) with new artwork URL") } - return onlyMatch + return onlyMatch as? PVGame } else { ELOG("We got to the unlikely scenario where an extension is possibly a CD binary file, or belongs to a system, and had multiple games that matched the filename under more than one core.") return nil diff --git a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/CDFileHandler.swift b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/CDFileHandler.swift index 186fd45012..bd38168764 100644 --- a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/CDFileHandler.swift +++ b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/CDFileHandler.swift @@ -6,6 +6,7 @@ // import Foundation +import PVPrimitives protocol CDFileHandling { func findAssociatedBinFileNames(for cueFileItem: ImportQueueItem) throws -> [String] diff --git a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporter.swift b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporter.swift index 575052a3ce..ebe0831cb1 100644 --- a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporter.swift +++ b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporter.swift @@ -102,22 +102,41 @@ public typealias GameImporterFinishedImportingGameHandler = (_ md5Hash: String, public typealias GameImporterFinishedGettingArtworkHandler = (_ artworkURL: String?) -> Void public protocol GameImporting { + + typealias ImportQueueItemType = ImportQueueItem + func initSystems() async var importStatus: String { get } - var importQueue: [ImportQueueItem] { get } + var importQueue: [ImportQueueItemType] { get } var processingState: ProcessingState { get } func addImport(_ item: ImportQueueItem) func addImports(forPaths paths: [URL]) + func addImports(forPaths paths: [URL], targetSystem: ImportQueueItemType.System) + func removeImports(at offsets: IndexSet) func startProcessing() - func sortImportQueueItems(_ importQueueItems: [ImportQueueItem]) -> [ImportQueueItem] + func sortImportQueueItems(_ importQueueItems: [ImportQueueItemType]) -> [ImportQueueItemType] - func importQueueContainsDuplicate(_ queue: [ImportQueueItem], ofItem queueItem: ImportQueueItem) -> Bool + func importQueueContainsDuplicate(_ queue: [ImportQueueItemType], ofItem queueItem: ImportQueueItemType) -> Bool + + var importStartedHandler: GameImporterImportStartedHandler? { get set } + /// Closure called when import completes + var completionHandler: GameImporterCompletionHandler? { get set } + /// Closure called when a game finishes importing + var finishedImportHandler: GameImporterFinishedImportingGameHandler? { get set } + /// Closure called when artwork finishes downloading + var finishedArtworkHandler: GameImporterFinishedGettingArtworkHandler? { get set } + + /// Spotlight Handerls + /// Closure called when spotlight completes + var spotlightCompletionHandler: GameImporterCompletionHandler? { get set } + /// Closure called when a game finishes importing + var spotlightFinishedImportHandler: GameImporterFinishedImportingGameHandler? { get set } } @@ -182,9 +201,9 @@ public final class GameImporter: GameImporting, ObservableObject { public var processingState: ProcessingState = .idle // Observable state for processing status internal var gameImporterFileService:GameImporterFileServicing - internal var gameImporterDatabaseService:GameImporterDatabaseServicing - internal var gameImporterSystemsService:GameImporterSystemsServicing - internal var gameImporterArtworkImporter:ArtworkImporting + internal var gameImporterDatabaseService:any GameImporterDatabaseServicing + internal var gameImporterSystemsService:any GameImporterSystemsServicing + internal var gameImporterArtworkImporter:any ArtworkImporting internal var cdRomFileHandler:CDFileHandling // MARK: - Paths @@ -198,7 +217,7 @@ public final class GameImporter: GameImporting, ObservableObject { /// Path to the BIOS directory public var biosPath: URL { get { Paths.biosesPath }} - public var databaseService: GameImporterDatabaseServicing { + public var databaseService: any GameImporterDatabaseServicing { return gameImporterDatabaseService } @@ -360,7 +379,7 @@ public final class GameImporter: GameImporting, ObservableObject { } } - public func addImports(forPaths paths: [URL], targetSystem: PVSystem) { + public func addImports(forPaths paths: [URL], targetSystem: ImportQueueItem.System) { importQueueLock.lock() defer { importQueueLock.unlock() } diff --git a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterDatabaseService.swift b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterDatabaseService.swift index c904ca5e05..9d1e12e635 100644 --- a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterDatabaseService.swift +++ b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterDatabaseService.swift @@ -22,20 +22,31 @@ import Perception import SwiftUI public protocol GameImporterDatabaseServicing { + // TODO: Make me more generic +// associatedtype GameType: PVGameLibraryEntry + typealias GameType = PVGame // PVGameLibraryEntry + func setOpenVGDB(_ vgdb: OpenVGDB) func setRomsPath(url:URL) func importGameIntoDatabase(queueItem: ImportQueueItem) async throws func importBIOSIntoDatabase(queueItem: ImportQueueItem) async throws - func getUpdatedGameInfo(for game: PVGame, forceRefresh: Bool) -> PVGame - func getArtwork(forGame game: PVGame) async -> PVGame + func getUpdatedGameInfo(for game: GameType, forceRefresh: Bool) -> GameType + func getArtwork(forGame game: GameType) async -> GameType +} + +extension CharacterSet { + var GameImporterDatabaseServiceCharset: CharacterSet { + GameImporterDatabaseServiceCharset + } } +fileprivate let GameImporterDatabaseServiceCharset: CharacterSet = { + var c = CharacterSet.punctuationCharacters + c.remove(charactersIn: ",-+&.'") + return c +}() class GameImporterDatabaseService : GameImporterDatabaseServicing { - static var charset: CharacterSet = { - var c = CharacterSet.punctuationCharacters - c.remove(charactersIn: ",-+&.'") - return c - }() + var romsPath:URL? var openVGDB: OpenVGDB? @@ -81,7 +92,7 @@ class GameImporterDatabaseService : GameImporterDatabaseServicing { await saveRelativePath(existingGame, partialPath: partialPath, file: queueItem.url) } else { DLOG("No existing game found, starting import to database") - try await self.importToDatabaseROM(forItem: queueItem, system: targetSystem, relatedFiles: nil) + try await self.importToDatabaseROM(forItem: queueItem, system: targetSystem as! AnySystem, relatedFiles: nil) } } @@ -118,7 +129,7 @@ class GameImporterDatabaseService : GameImporterDatabaseServicing { } /// Imports a ROM to the database - internal func importToDatabaseROM(forItem queueItem: ImportQueueItem, system: PVSystem, relatedFiles: [URL]?) async throws { + internal func importToDatabaseROM(forItem queueItem: ImportQueueItem, system: AnySystem, relatedFiles: [URL]?) async throws { guard let _ = queueItem.destinationUrl else { //how did we get here, throw? @@ -134,6 +145,10 @@ class GameImporterDatabaseService : GameImporterDatabaseServicing { DLOG("Creating game object with title: \(title), partialPath: \(partialPath)") + guard let system = RomDatabase.sharedInstance.object(ofType: PVSystem.self, wherePrimaryKeyEquals: system.identifier) else { + throw GameImporterError.noSystemMatched + } + let file = PVFile(withURL: queueItem.destinationUrl!) let game = PVGame(withFile: file, system: system) game.romPath = partialPath @@ -371,7 +386,7 @@ class GameImporterDatabaseService : GameImporterDatabaseServicing { if resultsMaybe == nil || resultsMaybe!.isEmpty { //PVEmulatorConfiguration.supportedROMFileExtensions.contains(game.file.url.pathExtension.lowercased()) { let fileName: String = game.file.url.lastPathComponent // Remove any extraneous stuff in the rom name such as (U), (J), [T+Eng] etc - let nonCharRange: NSRange = (fileName as NSString).rangeOfCharacter(from: GameImporterDatabaseService.charset) + let nonCharRange: NSRange = (fileName as NSString).rangeOfCharacter(from: GameImporterDatabaseServiceCharset) var gameTitleLen: Int if nonCharRange.length > 0, nonCharRange.location > 1 { gameTitleLen = nonCharRange.location - 1 diff --git a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterFileService.swift b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterFileService.swift index c0b57cc09a..e08d2a6a44 100644 --- a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterFileService.swift +++ b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterFileService.swift @@ -8,6 +8,7 @@ import Foundation import PVSupport import RealmSwift +import PVPrimitives protocol GameImporterFileServicing { func moveImportItem(toAppropriateSubfolder queueItem: ImportQueueItem) async throws @@ -91,7 +92,7 @@ class GameImporterFileService : GameImporterFileServicing { // MARK: - Utility - internal func moveChildImports(forQueueItem queueItem:ImportQueueItem, to destinationFolder:URL) async throws { + internal func moveChildImports(forQueueItem queueItem: ImportQueueItem, to destinationFolder: URL) async throws { guard !queueItem.childQueueItems.isEmpty else { return } diff --git a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterSystemsService.swift b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterSystemsService.swift index 24cd4861fa..384f0e54bc 100644 --- a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterSystemsService.swift +++ b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporterSystemsService.swift @@ -22,13 +22,21 @@ import Perception import SwiftUI protocol GameImporterSystemsServicing { + // TODO: Make me more generic + // associatedtype GameType: PVGameLibraryEntry + typealias GameType = PVGame // PVGameLibraryEntry + typealias SystemType = PVSystem //AnySystem + func setOpenVGDB(_ vgdb: OpenVGDB) func setExtensionsToSystemMapping(_ mapping: [String: [String]]) - func determineSystems(for queueItem: ImportQueueItem) async throws -> [PVSystem] - func findAnyCurrentGameThatCouldBelongToAnyOfTheseSystems(_ systems: [PVSystem]?, romFilename: String) -> [PVGame]? + func determineSystems(for queueItem: ImportQueueItem) async throws -> [SystemType] + func findAnyCurrentGameThatCouldBelongToAnyOfTheseSystems(_ systems: [AnySystem]?, romFilename: String) -> [GameType]? } -class GameImporterSystemsService : GameImporterSystemsServicing { +class GameImporterSystemsService: GameImporterSystemsServicing { + typealias GameType = PVGame + + var openVGDB: OpenVGDB? /// Map of ROM extensions to their corresponding system identifiers var romExtensionToSystemsMap = [String: [String]]() @@ -46,7 +54,7 @@ class GameImporterSystemsService : GameImporterSystemsServicing { } /// Determines the system for a given candidate file - public func determineSystems(for queueItem: ImportQueueItem) async throws -> [PVSystem] { + public func determineSystems(for queueItem: ImportQueueItem) async throws -> [SystemType] { guard let md5 = queueItem.md5?.uppercased() else { throw GameImporterError.couldNotCalculateMD5 } @@ -80,7 +88,7 @@ class GameImporterSystemsService : GameImporterSystemsServicing { } } - var matchingSystems:[PVSystem] = [] + var matchingSystems:[SystemType] = [] // Try to find system by MD5 using OpenVGDB if let results = try openVGDB?.searchDatabase(usingKey: "romHashMD5", value: md5), @@ -135,13 +143,13 @@ class GameImporterSystemsService : GameImporterSystemsServicing { } /// Determines the system for a given candidate file - private func determineSystemsFromContent(for queueItem: ImportQueueItem, possibleSystems: [PVSystem]) throws -> [PVSystem] { + private func determineSystemsFromContent(for queueItem: ImportQueueItem, possibleSystems: [SystemType]) throws -> [SystemType] { // Implement logic to determine system based on file content or metadata // This could involve checking file headers, parsing content, or using a database of known games let fileName = queueItem.url.deletingPathExtension().lastPathComponent - var matchedSystems:[PVSystem] = [] + var matchedSystems:[SystemType] = [] for system in possibleSystems { do { if let results = try openVGDB?.searchDatabase(usingFilename: fileName, systemID: system.openvgDatabaseID), @@ -234,7 +242,7 @@ class GameImporterSystemsService : GameImporterSystemsServicing { } //TODO: is this called? - internal func determineSystemByMD5(_ queueItem: ImportQueueItem) async throws -> PVSystem? { + internal func determineSystemByMD5(_ queueItem: ImportQueueItem) async throws -> SystemType? { guard let md5 = queueItem.md5?.uppercased() else { throw GameImporterError.couldNotCalculateMD5 } @@ -256,7 +264,7 @@ class GameImporterSystemsService : GameImporterSystemsServicing { //TODO: is this called? /// Determines the systems for a given path - internal func determineSystems(for path: URL, chosenSystem: System?) throws -> [PVSystem] { + internal func determineSystems(for path: URL, chosenSystem: SystemType?) throws -> [SystemType] { if let chosenSystem = chosenSystem { if let system = RomDatabase.systemCache[chosenSystem.identifier] { return [system] @@ -268,7 +276,7 @@ class GameImporterSystemsService : GameImporterSystemsServicing { } /// Finds any current game that could belong to any of the given systems - func findAnyCurrentGameThatCouldBelongToAnyOfTheseSystems(_ systems: [PVSystem]?, romFilename: String) -> [PVGame]? { + func findAnyCurrentGameThatCouldBelongToAnyOfTheseSystems(_ systems: [AnySystem]?, romFilename: String) -> [GameType]? { // Check if existing ROM let allGames = RomDatabase.gamesCache.values.filter ({ @@ -475,7 +483,7 @@ class GameImporterSystemsService : GameImporterSystemsServicing { /// Checks if a file content matches a given system - private func doesFileContentMatch(_ queueItem: ImportQueueItem, forSystem system: PVSystem) -> Bool { + private func doesFileContentMatch(_ queueItem: ImportQueueItem, forSystem system: AnySystem) -> Bool { // Implement system-specific file content matching logic here // This could involve checking file headers, file structure, or other system-specific traits // For now, we'll return false as a placeholder diff --git a/PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVLibraryEntry.swift b/PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVLibraryEntry.swift index 42a5efcb09..38ff989af1 100644 --- a/PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVLibraryEntry.swift +++ b/PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVLibraryEntry.swift @@ -21,6 +21,7 @@ public protocol PVRecentGameLibraryEntry: PVLibraryEntry { dynamic var core: PVCore? { get } } +public typealias AnyPVGameLibraryEntry = any PVGameLibraryEntry public protocol PVGameLibraryEntry: PVLibraryEntry { dynamic var title: String { get } @@ -30,7 +31,7 @@ public protocol PVGameLibraryEntry: PVLibraryEntry { dynamic var file: PVFile! { get } var relatedFiles: List { get } - dynamic var customArtworkURL: String { get } + dynamic var customArtworkURL: String { get set } dynamic var originalArtworkURL: String { get } dynamic var originalArtworkFile: PVImageFile? { get } diff --git a/PVPrimitives/Sources/PVPrimitives/Protocols/SystemProtocol.swift b/PVPrimitives/Sources/PVPrimitives/Protocols/SystemProtocol.swift index 8671c22726..cdc52034d1 100644 --- a/PVPrimitives/Sources/PVPrimitives/Protocols/SystemProtocol.swift +++ b/PVPrimitives/Sources/PVPrimitives/Protocols/SystemProtocol.swift @@ -8,7 +8,10 @@ import Foundation -public protocol SystemProtocol { +public typealias _SystemProtocol = SystemProtocol & Identifiable & ObservableObject & Hashable +public typealias AnySystem = any _SystemProtocol + +public protocol SystemProtocol : Hashable { associatedtype BIOSInfoProviderType: BIOSInfoProvider var name: String { get } @@ -43,6 +46,15 @@ public protocol SystemProtocol { } // MARK: Default Implimentations +public extension Identifiable where Self: SystemProtocol { + public var id: String { identifier } +} + +public extension Hashable where Self: SystemProtocol { + public func hash(into hasher: inout Hasher) { + hasher.combine(identifier) + } +} public extension SystemProtocol { var options: SystemOptions { From bc360daf8341c76f2a042362ce5d25f3dfb8d964 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 02:33:02 -0500 Subject: [PATCH 07/16] importstatusview work with any GameImporting Signed-off-by: Joseph Mattiello --- PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift index d5c41f303a..30017790b6 100644 --- a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift +++ b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift @@ -36,11 +36,11 @@ func iconNameForFileType(_ type: FileType) -> String { public struct ImportStatusView: View { @ObservedObject public var updatesController: PVGameLibraryUpdatesController - public var gameImporter:GameImporter + public var gameImporter:any GameImporting public weak var delegate:ImportStatusDelegate! public var dismissAction: (() -> Void)? = nil - public init(updatesController: PVGameLibraryUpdatesController, gameImporter:GameImporter, delegate:ImportStatusDelegate, dismissAction: (() -> Void)? = nil) { + public init(updatesController: PVGameLibraryUpdatesController, gameImporter: any GameImporting, delegate: ImportStatusDelegate, dismissAction: (() -> Void)? = nil) { self.updatesController = updatesController self.gameImporter = gameImporter self.delegate = delegate From 8b62c5bf24e7864b342e7893686dc3749d157ef8 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 02:33:24 -0500 Subject: [PATCH 08/16] PVGameLibraryUpdatesController work with any GameImporter Signed-off-by: Joseph Mattiello --- .../Game Library/PVGameLibraryUpdatesController.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/PVUI/Sources/PVUIBase/Game Library/PVGameLibraryUpdatesController.swift b/PVUI/Sources/PVUIBase/Game Library/PVGameLibraryUpdatesController.swift index 4e43cad59e..53db68d48b 100644 --- a/PVUI/Sources/PVUIBase/Game Library/PVGameLibraryUpdatesController.swift +++ b/PVUI/Sources/PVUIBase/Game Library/PVGameLibraryUpdatesController.swift @@ -27,7 +27,7 @@ public final class PVGameLibraryUpdatesController: ObservableObject { @Published public var conflicts: [ConflictsController.ConflictItem] = [] - public let gameImporter: GameImporter + public var gameImporter: any GameImporting private let directoryWatcher: DirectoryWatcher private let conflictsWatcher: ConflictsWatcher @@ -39,7 +39,7 @@ public final class PVGameLibraryUpdatesController: ObservableObject { AppState.shared.hudCoordinator } - public init(gameImporter: GameImporter, importPath: URL? = nil) { + public init(gameImporter: any GameImporting, importPath: URL? = nil) { let importPath = importPath ?? Paths.romsImportPath self.gameImporter = gameImporter @@ -90,11 +90,13 @@ public final class PVGameLibraryUpdatesController: ObservableObject { } } + var statusExtractionSateObserver: Task? private func setupExtractionStatusObserver() { let taskID = UUID() DLOG("Starting extraction status observer task: \(taskID)") + statusExtractionSateObserver?.cancel() - Task { + statusExtractionSateObserver = Task { defer { DLOG("Ending extraction status observer task: \(taskID)") } var hideTask: Task? @@ -123,7 +125,7 @@ public final class PVGameLibraryUpdatesController: ObservableObject { isHidingHUD = false } case .idle: - Task { + hideTask = Task { let hudState: HudState = .hidden await self.hudCoordinator.updateHUD(hudState) } From de6d8328e435d769510c05ead9fe904d3fd2da3e Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 02:33:46 -0500 Subject: [PATCH 09/16] UITesting mock game importer Signed-off-by: Joseph Mattiello --- .../MockGameImporter.swift | 73 ++++++++++++++++--- .../MockImportStatusDelegate.swift | 2 +- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift index c6e9de4c6b..12b85d694a 100644 --- a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift +++ b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift @@ -7,32 +7,66 @@ import PVSwiftUI import SwiftUI +import Combine +import PVPrimitives -class MockGameImporter: GameImporting { - func initSystems() async { - - } +class MockGameImporter: GameImporting, ObservableObject { + @Published private(set) var importStatus: String = "Ready" + @Published var importQueue: [ImportQueueItem] = [] + @Published private(set) var processingState: ProcessingState = .idle - var importStatus: String { - "" + func initSystems() async { + // Mock implementation - no real systems to initialize + importStatus = "Systems initialized" } - var importQueue: [ImportQueueItem] = [] - - var processingState: ProcessingState = .idle - func addImport(_ item: ImportQueueItem) { + if !importQueueContainsDuplicate(importQueue, ofItem: item) { + importQueue.append(item) + importStatus = "Added \(item.url.lastPathComponent) to queue" + } + } + + func addImports(forPaths paths: [URL], targetSystem: any SystemProtocol) { } + + func addImports(forPaths paths: [URL]) { - + for path in paths { + let item = ImportQueueItem(url: path, fileType: .unknown) + addImport(item) + } + importStatus = "Added \(paths.count) items to queue" } func removeImports(at offsets: IndexSet) { - + importQueue.remove(atOffsets: offsets) + importStatus = "Removed \(offsets.count) items from queue" } + func startProcessing() { + guard !importQueue.isEmpty else { + importStatus = "No items in queue" + return + } + processingState = .processing + + // Simulate processing by marking all items as successful after a delay + Task { + importStatus = "Processing \(importQueue.count) items..." + + // Simulate some processing time + try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds + + for index in importQueue.indices { + importQueue[index].status = .success + } + + processingState = .idle + importStatus = "Completed processing \(importQueue.count) items" + } } func sortImportQueueItems(_ importQueueItems: [ImportQueueItem]) -> [ImportQueueItem] { @@ -42,4 +76,19 @@ class MockGameImporter: GameImporting { func importQueueContainsDuplicate(_ queue: [ImportQueueItem], ofItem queueItem: ImportQueueItem) -> Bool { queue.contains(where: { $0.url == queueItem.url }) } + + var importStartedHandler: GameImporterImportStartedHandler? = nil + /// Closure called when import completes + var completionHandler: GameImporterCompletionHandler? = nil + /// Closure called when a game finishes importing + var finishedImportHandler: GameImporterFinishedImportingGameHandler? = nil + /// Closure called when artwork finishes downloading + var finishedArtworkHandler: GameImporterFinishedGettingArtworkHandler? = nil + + /// Spotlight Handerls + /// Closure called when spotlight completes + var spotlightCompletionHandler: GameImporterCompletionHandler? = nil + /// Closure called when a game finishes importing + var spotlightFinishedImportHandler: GameImporterFinishedImportingGameHandler? = nil + } diff --git a/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift b/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift index 7e073ca38f..f232506aa8 100644 --- a/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift +++ b/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift @@ -9,7 +9,7 @@ import PVSwiftUI class MockImportStatusDriverData: ObservableObject { @MainActor - let gameImporter = AppState.shared.gameImporter ?? GameImporter.shared + let gameImporter = MockGameImporter() //AppState.shared.gameImporter ?? GameImporter.shared @MainActor let pvgamelibraryUpdatesController: PVGameLibraryUpdatesController From f365625d2966aaf4ce359340b0c5ca98390069dd Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 02:45:47 -0500 Subject: [PATCH 10/16] GameImporter slightly more flexible with targetSystem: AnySystem Signed-off-by: Joseph Mattiello --- .../Importer/Services/GameImporter/GameImporter.swift | 6 +++--- .../UITesting/ImportStatusTesting/MockGameImporter.swift | 2 +- UITesting/UITesting/UITestingApp.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporter.swift b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporter.swift index ebe0831cb1..f87c053387 100644 --- a/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporter.swift +++ b/PVLibrary/Sources/PVLibrary/Importer/Services/GameImporter/GameImporter.swift @@ -115,7 +115,7 @@ public protocol GameImporting { func addImport(_ item: ImportQueueItem) func addImports(forPaths paths: [URL]) - func addImports(forPaths paths: [URL], targetSystem: ImportQueueItemType.System) + func addImports(forPaths paths: [URL], targetSystem: AnySystem) func removeImports(at offsets: IndexSet) func startProcessing() @@ -379,13 +379,13 @@ public final class GameImporter: GameImporting, ObservableObject { } } - public func addImports(forPaths paths: [URL], targetSystem: ImportQueueItem.System) { + public func addImports(forPaths paths: [URL], targetSystem: AnySystem) { importQueueLock.lock() defer { importQueueLock.unlock() } for path in paths { var item = ImportQueueItem(url: path, fileType: .unknown) - item.userChosenSystem = targetSystem + item.userChosenSystem?.identifier = targetSystem.identifier self.addImportItemToQueue(item) } } diff --git a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift index 12b85d694a..977d0defba 100644 --- a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift +++ b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift @@ -27,7 +27,7 @@ class MockGameImporter: GameImporting, ObservableObject { } } - func addImports(forPaths paths: [URL], targetSystem: any SystemProtocol) { + func addImports(forPaths paths: [URL], targetSystem: AnySystem) { } diff --git a/UITesting/UITesting/UITestingApp.swift b/UITesting/UITesting/UITestingApp.swift index b6eadc476a..de6fff3795 100644 --- a/UITesting/UITesting/UITestingApp.swift +++ b/UITesting/UITesting/UITestingApp.swift @@ -180,7 +180,7 @@ struct MainView_Previews: PreviewProvider { } } .sheet(isPresented: $showingSettings) { - let gameImporter = GameImporter.shared + let gameImporter = MockGameImporter() let pvgamelibraryUpdatesController = PVGameLibraryUpdatesController(gameImporter: gameImporter) let menuDelegate = MockPVMenuDelegate() From 79672bd0b0363e2da1e158e7c8954fd915bf2a34 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 03:10:24 -0500 Subject: [PATCH 11/16] ImportStatus case iterable Signed-off-by: Joseph Mattiello --- .../Sources/PVLibrary/Importer/Models/ImportQueueItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PVLibrary/Sources/PVLibrary/Importer/Models/ImportQueueItem.swift b/PVLibrary/Sources/PVLibrary/Importer/Models/ImportQueueItem.swift index a88cbdfce3..fe5c92a092 100644 --- a/PVLibrary/Sources/PVLibrary/Importer/Models/ImportQueueItem.swift +++ b/PVLibrary/Sources/PVLibrary/Importer/Models/ImportQueueItem.swift @@ -10,7 +10,7 @@ import PVPrimitives import Perception // Enum to define the possible statuses of each import -public enum ImportStatus: Int, CustomStringConvertible { +public enum ImportStatus: Int, CustomStringConvertible, CaseIterable { case conflict // Indicates additional action needed by user after successful import case partial //indicates the item is waiting for associated files before it could be processed From c08e0bc19ee074c24ec13dbbd1b2d5e92fd28b6c Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 03:10:35 -0500 Subject: [PATCH 12/16] mock import enough data to test ui Signed-off-by: Joseph Mattiello --- .../ImportStatusTesting/MockGameImporter.swift | 17 ++++++++++++++--- .../MockImportStatusDelegate.swift | 9 +++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift index 977d0defba..236bdbdb40 100644 --- a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift +++ b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift @@ -12,7 +12,13 @@ import PVPrimitives class MockGameImporter: GameImporting, ObservableObject { @Published private(set) var importStatus: String = "Ready" - @Published var importQueue: [ImportQueueItem] = [] + @Published var importQueue: [ImportQueueItem] = [ + ImportQueueItem(url: .init(fileURLWithPath: "test.bin"), fileType: .unknown), + ImportQueueItem(url: .init(fileURLWithPath: "test.jpg"), fileType: .artwork), + ImportQueueItem(url: .init(fileURLWithPath: "bios.bin"), fileType: .bios), + ImportQueueItem(url: .init(fileURLWithPath: "test.cue"), fileType: .cdRom), + ImportQueueItem(url: .init(fileURLWithPath: "test.jag"), fileType: .game) + ] @Published private(set) var processingState: ProcessingState = .idle func initSystems() async { @@ -28,7 +34,12 @@ class MockGameImporter: GameImporting, ObservableObject { } func addImports(forPaths paths: [URL], targetSystem: AnySystem) { - + for path in paths { + let item = ImportQueueItem(url: path, fileType: .unknown) + item.userChosenSystem = targetSystem as? PVSystem // TODO: change when generic system types supported + addImport(item) + } + importStatus = "Added \(paths.count) items to queue" } @@ -61,7 +72,7 @@ class MockGameImporter: GameImporting, ObservableObject { try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds for index in importQueue.indices { - importQueue[index].status = .success + importQueue[index].status = ImportStatus(rawValue: index % ImportStatus.allCases.count)! } processingState = .idle diff --git a/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift b/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift index f232506aa8..a347c866fc 100644 --- a/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift +++ b/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift @@ -27,10 +27,15 @@ extension MockImportStatusDriverData: ImportStatusDelegate { } func addImportsAction() { - + let importItems: [ImportQueueItem] = [ + .init(url: .init(string: "test.jag")!, fileType: .unknown) + ] + importItems.forEach { + gameImporter.addImport($0) + } } func forceImportsAction() { - + gameImporter.startProcessing() } } From d4278184ddb1c4ba15ed7e623dd0a26e79315d8f Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 14:34:30 -0500 Subject: [PATCH 13/16] fix themeing no import status view Signed-off-by: Joseph Mattiello --- .../PVSwiftUI/Imports/ImportStatusView.swift | 10 +++++++++- .../PVSwiftUI/Imports/ImportTaskRowView.swift | 20 ++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift index 30017790b6..7557980f43 100644 --- a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift +++ b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift @@ -40,6 +40,9 @@ public struct ImportStatusView: View { public weak var delegate:ImportStatusDelegate! public var dismissAction: (() -> Void)? = nil + @ObservedObject private var themeManager = ThemeManager.shared + var currentPalette: any UXThemePalette { themeManager.currentPalette } + public init(updatesController: PVGameLibraryUpdatesController, gameImporter: any GameImporting, delegate: ImportStatusDelegate, dismissAction: (() -> Void)? = nil) { self.updatesController = updatesController self.gameImporter = gameImporter @@ -71,7 +74,10 @@ public struct ImportStatusView: View { .toolbar { ToolbarItemGroup(placement: .topBarLeading, content: { - Button("Done") { delegate.dismissAction() + if dismissAction != nil { + Button("Done") { delegate.dismissAction() + } + .tint(currentPalette.defaultTintColor?.swiftUIColor) } }) ToolbarItemGroup(placement: .topBarTrailing, @@ -79,9 +85,11 @@ public struct ImportStatusView: View { Button("Add Files") { delegate?.addImportsAction() } + .tint(currentPalette.defaultTintColor?.swiftUIColor) Button("Begin") { delegate?.forceImportsAction() } + .tint(currentPalette.defaultTintColor?.swiftUIColor) }) } } diff --git a/PVUI/Sources/PVSwiftUI/Imports/ImportTaskRowView.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportTaskRowView.swift index 11bb685d38..1644127aaa 100644 --- a/PVUI/Sources/PVSwiftUI/Imports/ImportTaskRowView.swift +++ b/PVUI/Sources/PVSwiftUI/Imports/ImportTaskRowView.swift @@ -33,7 +33,8 @@ struct ImportTaskRowView: View { let item: ImportQueueItem @State private var isNavigatingToSystemSelection = false @ObservedObject private var themeManager = ThemeManager.shared - + var currentPalette: any UXThemePalette { themeManager.currentPalette } + var bgColor: Color { themeManager.currentPalette.settingsCellBackground?.swiftUIColor ?? themeManager.currentPalette.menuBackground.swiftUIColor } @@ -87,17 +88,22 @@ struct ImportTaskRowView: View { .fill(bgColor) .shadow(radius: 2) ) + //.background(currentPalette.gameLibraryBackground.swiftUIColor) .onTapGesture { if item.status == .conflict { isNavigatingToSystemSelection = true } } - .background( - NavigationLink(destination: SystemSelectionView(item: item), isActive: $isNavigatingToSystemSelection) { - EmptyView() - } - .hidden() - ) +// .background( +// NavigationLink(destination: SystemSelectionView(item: item), isActive: $isNavigatingToSystemSelection) { +// EmptyView() +// } +// .hidden() +// ) } } } + +#Preview { + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.jpg"), fileType: .artwork)) +} From 86d4495ba530acf21b88f44a07800d764b56727a Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 18:14:07 -0500 Subject: [PATCH 14/16] Better pvsystem testing and ux tweaks Signed-off-by: Joseph Mattiello --- .../RealmPlatform/Entities/PVSystem.swift | 17 +++ .../MockGameImporter.swift | 9 +- .../UITesting/Imports/ImportStatusView.swift | 122 ++++++++++++++++ .../UITesting/Imports/ImportTaskRowView.swift | 132 ++++++++++++++++++ .../Imports/SystemSelectionView.swift | 70 ++++++++++ 5 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 UITesting/UITesting/Imports/ImportStatusView.swift create mode 100644 UITesting/UITesting/Imports/ImportTaskRowView.swift create mode 100644 UITesting/UITesting/Imports/SystemSelectionView.swift diff --git a/PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVSystem.swift b/PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVSystem.swift index 6c67ddc473..edb212ab34 100644 --- a/PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVSystem.swift +++ b/PVLibrary/Sources/PVRealm/RealmPlatform/Entities/PVSystem.swift @@ -140,6 +140,23 @@ public final class PVSystem: Object, Identifiable, SystemProtocol { } } +/// Mock testing +public extension PVSystem { + public convenience init( + identifier: String, + name: String, + shortName: String, + manufacturer: String, + screenType: ScreenType = .crt + ) { + self.init() + self.identifier = id + self.name = name + self.shortName = shortName + self.manufacturer = manufacturer + } +} + public extension PVSystem { var screenType: ScreenType { get { diff --git a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift index 236bdbdb40..1701f58dcc 100644 --- a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift +++ b/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift @@ -13,7 +13,14 @@ import PVPrimitives class MockGameImporter: GameImporting, ObservableObject { @Published private(set) var importStatus: String = "Ready" @Published var importQueue: [ImportQueueItem] = [ - ImportQueueItem(url: .init(fileURLWithPath: "test.bin"), fileType: .unknown), + { + let item = ImportQueueItem(url: .init(fileURLWithPath: "test.bin"), fileType: .unknown) + item.systems = [ + PVSystem(identifier: "com.provenance.jaguar", name: "Jaguar", shortName: "Jag", manufacturer: "Atari", screenType: .crt), + PVSystem(identifier: "com.provenance.jaguarcd", name: "Jaguar CD", shortName: "JagCD", manufacturer: "Atari", screenType: .crt) + ] + return item + }(), ImportQueueItem(url: .init(fileURLWithPath: "test.jpg"), fileType: .artwork), ImportQueueItem(url: .init(fileURLWithPath: "bios.bin"), fileType: .bios), ImportQueueItem(url: .init(fileURLWithPath: "test.cue"), fileType: .cdRom), diff --git a/UITesting/UITesting/Imports/ImportStatusView.swift b/UITesting/UITesting/Imports/ImportStatusView.swift new file mode 100644 index 0000000000..18061f76a8 --- /dev/null +++ b/UITesting/UITesting/Imports/ImportStatusView.swift @@ -0,0 +1,122 @@ + +// +// ImportStatusView.swift +// PVUI +// +// Created by David Proskin on 10/31/24. +// + +import SwiftUI +import PVUIBase +import PVLibrary +import PVThemes +import Perception + +public protocol ImportStatusDelegate : AnyObject { + func dismissAction() + func addImportsAction() + func forceImportsAction() +} + +func iconNameForFileType(_ type: FileType) -> String { + + switch type { + case .bios: + return "bios_filled" + case .artwork: + return "image_icon_256" + case .game: + return "file_icon_256" + case .cdRom: + return "cd_icon_256" + case .unknown: + return "questionMark" + } +} + +public struct ImportStatusView: View { + @ObservedObject + public var updatesController: PVGameLibraryUpdatesController + public var gameImporter:any GameImporting + public weak var delegate:ImportStatusDelegate! + public var dismissAction: (() -> Void)? = nil + + @ObservedObject private var themeManager = ThemeManager.shared + var currentPalette: any UXThemePalette { themeManager.currentPalette } + + public init(updatesController: PVGameLibraryUpdatesController, gameImporter: any GameImporting, delegate: ImportStatusDelegate, dismissAction: (() -> Void)? = nil) { + self.updatesController = updatesController + self.gameImporter = gameImporter + self.delegate = delegate + self.dismissAction = dismissAction + } + + private func deleteItems(at offsets: IndexSet) { + gameImporter.removeImports(at: offsets) + } + + public var body: some View { + WithPerceptionTracking { + NavigationView { + List { + if gameImporter.importQueue.isEmpty { + Text("No items in the import queue") + .foregroundColor(.secondary) + .padding() + } else { + ForEach(gameImporter.importQueue) { item in + ImportTaskRowView(item: item).id(item.id) + }.onDelete( + perform: deleteItems + ) + } + } + .navigationTitle("Import Queue") + .toolbar { + ToolbarItemGroup(placement: .topBarLeading, + content: { + if dismissAction != nil { + Button("Done") { delegate.dismissAction() + } + .tint(currentPalette.defaultTintColor?.swiftUIColor) + } + }) + ToolbarItemGroup(placement: .topBarTrailing, + content: { + Button("Add Files") { + delegate?.addImportsAction() + } + .tint(currentPalette.defaultTintColor?.swiftUIColor) + Button("Begin") { + delegate?.forceImportsAction() + } + .tint(currentPalette.defaultTintColor?.swiftUIColor) + }) + } + .background(currentPalette.gameLibraryBackground.swiftUIColor) + } + .background(currentPalette.gameLibraryBackground.swiftUIColor) + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.visible) + } + } +} + +#if DEBUG +#Preview { + @ObservedObject var themeManager = ThemeManager.shared + var currentPalette: any UXThemePalette { themeManager.currentPalette } + + let mockImportStatusDriverData = MockImportStatusDriverData() + + ImportStatusView( + updatesController: mockImportStatusDriverData.pvgamelibraryUpdatesController, + gameImporter: mockImportStatusDriverData.gameImporter, + delegate: mockImportStatusDriverData) { + print("Import Status View Closed") + } + .onAppear { + themeManager.setCurrentPalette(CGAThemes.green.palette) + } +} +#endif diff --git a/UITesting/UITesting/Imports/ImportTaskRowView.swift b/UITesting/UITesting/Imports/ImportTaskRowView.swift new file mode 100644 index 0000000000..e0c9fa80ff --- /dev/null +++ b/UITesting/UITesting/Imports/ImportTaskRowView.swift @@ -0,0 +1,132 @@ +// +// ImportTaskRowView.swift +// PVUI +// +// Created by Joseph Mattiello on 11/17/24. +// + +import SwiftUI +import PVLibrary +import PVThemes +import Perception + +func iconNameForStatus(_ status: ImportStatus) -> String { + switch status { + + case .queued: + return "line.3.horizontal.circle" + case .processing: + return "progress.indicator" + case .success: + return "checkmark.circle.fill" + case .failure: + return "xmark.diamond.fill" + case .conflict: + return "exclamationmark.triangle.fill" + case .partial: + return "display.trianglebadge.exclamationmark" + } +} + +// Individual Import Task Row View +struct ImportTaskRowView: View { + let item: ImportQueueItem + @State private var isNavigatingToSystemSelection = false + @ObservedObject private var themeManager = ThemeManager.shared + var currentPalette: any UXThemePalette { themeManager.currentPalette } + + var bgColor: Color { + themeManager.currentPalette.settingsCellBackground?.swiftUIColor ?? themeManager.currentPalette.menuBackground.swiftUIColor + } + + var fgColor: Color { + themeManager.currentPalette.settingsCellText?.swiftUIColor ?? themeManager.currentPalette.menuText.swiftUIColor + } + + var secondaryFgColor: Color { + themeManager.currentPalette.settingsCellTextDetail?.swiftUIColor ?? .secondary + } + + var body: some View { + WithPerceptionTracking { + HStack { +// // TODO: add icon for fileType + VStack(alignment: .leading) { + Text(item.url.lastPathComponent) + .font(.headline) + .foregroundColor(fgColor) + + if item.fileType == .bios { + Text("BIOS") + .font(.subheadline) + .foregroundColor(fgColor) + } else if !item.systems.isEmpty { + Text("\(item.systems.count) systems") + .font(.subheadline) + .foregroundColor(secondaryFgColor) + } + + if item.status == .failure, let errorText = item.errorValue { + Text("Error Detail: \(errorText)") + .font(.subheadline) + .foregroundColor(secondaryFgColor) + } + } + + Spacer() + + VStack(alignment: .trailing) { + if item.status == .processing { + ProgressView() + .progressViewStyle(.circular) + .frame(width: 40, height: 40, alignment: .center) +// .tint(themeManager.currentPalette.defaultTintColor!) + } else { + Image(systemName: iconNameForStatus(item.status)) + .foregroundColor(item.status.color) + } + + if (item.childQueueItems.count > 0) { + Text("+\(item.childQueueItems.count) files") + .font(.subheadline) + .foregroundColor(fgColor) + } + } + + } + .padding() + .background( + RoundedRectangle(cornerRadius: 10) + .fill(bgColor) + .shadow(radius: 2) + ) + //.background(currentPalette.gameLibraryBackground.swiftUIColor) + .onTapGesture { + if item.status == .conflict { + isNavigatingToSystemSelection = true + } + } + .background( + NavigationLink(destination: SystemSelectionView(item: item), isActive: $isNavigatingToSystemSelection) { + EmptyView() + } + .hidden() + ) + } + } +} +#if DEBUG +import PVThemes +#Preview { + List { + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.jpg"), fileType: .artwork)) + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.bin"), fileType: .bios)) + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.iso"), fileType: .cdRom)) + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.jag"), fileType: .game)) + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.zip"), fileType: .unknown)) + } + .onAppear { + ThemeManager.shared.setCurrentPalette(CGAThemes.green.palette) + } +} +#endif diff --git a/UITesting/UITesting/Imports/SystemSelectionView.swift b/UITesting/UITesting/Imports/SystemSelectionView.swift new file mode 100644 index 0000000000..bacfbd6689 --- /dev/null +++ b/UITesting/UITesting/Imports/SystemSelectionView.swift @@ -0,0 +1,70 @@ +// +// SystemSelectionView.swift +// PVUI +// +// Created by David Proskin on 11/7/24. +// + +import SwiftUI +import PVLibrary + +struct SystemSelectionView: View { + @ObservedObject var item: ImportQueueItem + @Environment(\.presentationMode) var presentationMode + + var body: some View { + List { + ForEach(item.systems.sorted(by: { a, b in + return a.name <= b.name + }), id: \.self) { system in + Button(action: { + // Set the chosen system and update the status + item.userChosenSystem = system + item.status = .queued + // Dismiss the view + presentationMode.wrappedValue.dismiss() + }) { + Text(system.name) + .font(.headline) + .padding() + } + } + } + .navigationTitle("Select System") + } +} + +#if DEBUG +import PVPrimitives +#Preview { + + + let item: ImportQueueItem = { + let systems: [PVSystem] = [ + .init( + identifier: "com.provenance.jaguar", + name: "Jaguar", + shortName: "Jag", + manufacturer: "Atari", + screenType: .crt + ), + .init( + identifier: "com.provenance.jaguarcd", + name: "Jaguar CD", + shortName: "Jag CD", + manufacturer: "Atari", + screenType: .crt + ) + ] + + let item = ImportQueueItem(url: .init(fileURLWithPath: "Test.bin"), + fileType: .game) + item.systems = systems + return item + }() + + List { + SystemSelectionView(item: item) + } +} +#endif From 93eff722d323f113a04d11546889c82f1da28ed4 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 18:21:06 -0500 Subject: [PATCH 15/16] move ux files back to pvui after testing Signed-off-by: Joseph Mattiello --- .../MockGameImporter.swift | 0 .../MockImportStatusDelegate.swift | 0 .../PVSwiftUI/Imports/ImportStatusView.swift | 33 ++++++++---- .../PVSwiftUI/Imports/ImportTaskRowView.swift | 53 +++++++++++++------ .../Imports/SystemSelectionView.swift | 32 +++++++++++ .../xcshareddata/swiftpm/Package.resolved | 4 +- 6 files changed, 94 insertions(+), 28 deletions(-) rename {UITesting/UITesting => PVUI/Sources/PVSwiftUI/Imports}/ImportStatusTesting/MockGameImporter.swift (100%) rename {UITesting/UITesting => PVUI/Sources/PVSwiftUI/Imports}/ImportStatusTesting/MockImportStatusDelegate.swift (100%) diff --git a/UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockGameImporter.swift similarity index 100% rename from UITesting/UITesting/ImportStatusTesting/MockGameImporter.swift rename to PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockGameImporter.swift diff --git a/UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockImportStatusDelegate.swift similarity index 100% rename from UITesting/UITesting/ImportStatusTesting/MockImportStatusDelegate.swift rename to PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockImportStatusDelegate.swift diff --git a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift index 7557980f43..18061f76a8 100644 --- a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift +++ b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusView.swift @@ -7,6 +7,7 @@ // import SwiftUI +import PVUIBase import PVLibrary import PVThemes import Perception @@ -60,7 +61,7 @@ public struct ImportStatusView: View { List { if gameImporter.importQueue.isEmpty { Text("No items in the import queue") - .foregroundColor(.gray) + .foregroundColor(.secondary) .padding() } else { ForEach(gameImporter.importQueue) { item in @@ -92,20 +93,30 @@ public struct ImportStatusView: View { .tint(currentPalette.defaultTintColor?.swiftUIColor) }) } + .background(currentPalette.gameLibraryBackground.swiftUIColor) } + .background(currentPalette.gameLibraryBackground.swiftUIColor) .presentationDetents([.medium, .large]) .presentationDragIndicator(.visible) } } } -//#Preview { -// let gameImporter = AppState.shared.gameImporter ?? GameImporter.shared -// let pvgamelibraryUpdatesController = PVGameLibraryUpdatesController(gameImporter: gameImporter) -// let importDelegate = MockImportStatusDelegate() -// -// ImportStatusView( -// updatesController: pvgamelibraryUpdatesController, -// gameImporter: gameImporter, -// delegate: importDelegate) -//} +#if DEBUG +#Preview { + @ObservedObject var themeManager = ThemeManager.shared + var currentPalette: any UXThemePalette { themeManager.currentPalette } + + let mockImportStatusDriverData = MockImportStatusDriverData() + + ImportStatusView( + updatesController: mockImportStatusDriverData.pvgamelibraryUpdatesController, + gameImporter: mockImportStatusDriverData.gameImporter, + delegate: mockImportStatusDriverData) { + print("Import Status View Closed") + } + .onAppear { + themeManager.setCurrentPalette(CGAThemes.green.palette) + } +} +#endif diff --git a/PVUI/Sources/PVSwiftUI/Imports/ImportTaskRowView.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportTaskRowView.swift index 1644127aaa..e0c9fa80ff 100644 --- a/PVUI/Sources/PVSwiftUI/Imports/ImportTaskRowView.swift +++ b/PVUI/Sources/PVSwiftUI/Imports/ImportTaskRowView.swift @@ -38,37 +38,49 @@ struct ImportTaskRowView: View { var bgColor: Color { themeManager.currentPalette.settingsCellBackground?.swiftUIColor ?? themeManager.currentPalette.menuBackground.swiftUIColor } + + var fgColor: Color { + themeManager.currentPalette.settingsCellText?.swiftUIColor ?? themeManager.currentPalette.menuText.swiftUIColor + } + + var secondaryFgColor: Color { + themeManager.currentPalette.settingsCellTextDetail?.swiftUIColor ?? .secondary + } var body: some View { WithPerceptionTracking { HStack { - //TODO: add icon for fileType +// // TODO: add icon for fileType VStack(alignment: .leading) { Text(item.url.lastPathComponent) .font(.headline) + .foregroundColor(fgColor) + if item.fileType == .bios { Text("BIOS") .font(.subheadline) - .foregroundColor(themeManager.currentPalette.gameLibraryText.swiftUIColor) + .foregroundColor(fgColor) } else if !item.systems.isEmpty { Text("\(item.systems.count) systems") .font(.subheadline) - .foregroundColor(themeManager.currentPalette.gameLibraryText.swiftUIColor) + .foregroundColor(secondaryFgColor) } if item.status == .failure, let errorText = item.errorValue { Text("Error Detail: \(errorText)") .font(.subheadline) - .foregroundColor(themeManager.currentPalette.gameLibraryText.swiftUIColor) + .foregroundColor(secondaryFgColor) } - } Spacer() VStack(alignment: .trailing) { if item.status == .processing { - ProgressView().progressViewStyle(.circular).frame(width: 40, height: 40, alignment: .center) + ProgressView() + .progressViewStyle(.circular) + .frame(width: 40, height: 40, alignment: .center) +// .tint(themeManager.currentPalette.defaultTintColor!) } else { Image(systemName: iconNameForStatus(item.status)) .foregroundColor(item.status.color) @@ -77,7 +89,7 @@ struct ImportTaskRowView: View { if (item.childQueueItems.count > 0) { Text("+\(item.childQueueItems.count) files") .font(.subheadline) - .foregroundColor(themeManager.currentPalette.gameLibraryText.swiftUIColor) + .foregroundColor(fgColor) } } @@ -94,16 +106,27 @@ struct ImportTaskRowView: View { isNavigatingToSystemSelection = true } } -// .background( -// NavigationLink(destination: SystemSelectionView(item: item), isActive: $isNavigatingToSystemSelection) { -// EmptyView() -// } -// .hidden() -// ) + .background( + NavigationLink(destination: SystemSelectionView(item: item), isActive: $isNavigatingToSystemSelection) { + EmptyView() + } + .hidden() + ) } } } - +#if DEBUG +import PVThemes #Preview { - ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.jpg"), fileType: .artwork)) + List { + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.jpg"), fileType: .artwork)) + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.bin"), fileType: .bios)) + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.iso"), fileType: .cdRom)) + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.jag"), fileType: .game)) + ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.zip"), fileType: .unknown)) + } + .onAppear { + ThemeManager.shared.setCurrentPalette(CGAThemes.green.palette) + } } +#endif diff --git a/PVUI/Sources/PVSwiftUI/Imports/SystemSelectionView.swift b/PVUI/Sources/PVSwiftUI/Imports/SystemSelectionView.swift index 71074b7b8f..bacfbd6689 100644 --- a/PVUI/Sources/PVSwiftUI/Imports/SystemSelectionView.swift +++ b/PVUI/Sources/PVSwiftUI/Imports/SystemSelectionView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import PVLibrary struct SystemSelectionView: View { @ObservedObject var item: ImportQueueItem @@ -33,6 +34,37 @@ struct SystemSelectionView: View { } } +#if DEBUG +import PVPrimitives #Preview { + + let item: ImportQueueItem = { + let systems: [PVSystem] = [ + .init( + identifier: "com.provenance.jaguar", + name: "Jaguar", + shortName: "Jag", + manufacturer: "Atari", + screenType: .crt + ), + .init( + identifier: "com.provenance.jaguarcd", + name: "Jaguar CD", + shortName: "Jag CD", + manufacturer: "Atari", + screenType: .crt + ) + ] + + let item = ImportQueueItem(url: .init(fileURLWithPath: "Test.bin"), + fileType: .game) + item.systems = systems + return item + }() + + List { + SystemSelectionView(item: item) + } } +#endif diff --git a/UITesting/UITesting.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/UITesting/UITesting.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cf62b60ee1..26f3fbde11 100644 --- a/UITesting/UITesting.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/UITesting/UITesting.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -78,8 +78,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/FlineDev/FreemiumKit.git", "state" : { - "revision" : "68a3c35053afe0c564585da4f16315e1bbcda441", - "version" : "1.14.0" + "revision" : "ff45d8e3fc7af5255f97e059cc95524dfa320196", + "version" : "1.15.0" } }, { From 4081da779dcbe194bdcf4ba980ab590b72655776 Mon Sep 17 00:00:00 2001 From: Joseph Mattiello Date: Thu, 28 Nov 2024 18:25:31 -0500 Subject: [PATCH 16/16] fix uitesting build Signed-off-by: Joseph Mattiello --- .../MockGameImporter.swift | 76 ++++++---- .../MockImportStatusDelegate.swift | 16 +-- .../UITesting/Imports/ImportStatusView.swift | 122 ---------------- .../UITesting/Imports/ImportTaskRowView.swift | 132 ------------------ .../Imports/SystemSelectionView.swift | 70 ---------- 5 files changed, 54 insertions(+), 362 deletions(-) delete mode 100644 UITesting/UITesting/Imports/ImportStatusView.swift delete mode 100644 UITesting/UITesting/Imports/ImportTaskRowView.swift delete mode 100644 UITesting/UITesting/Imports/SystemSelectionView.swift diff --git a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockGameImporter.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockGameImporter.swift index 1701f58dcc..bc7581b0e8 100644 --- a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockGameImporter.swift +++ b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockGameImporter.swift @@ -10,37 +10,53 @@ import SwiftUI import Combine import PVPrimitives +public class MockGameImporter: GameImporting, ObservableObject { - @Published private(set) var importStatus: String = "Ready" - @Published var importQueue: [ImportQueueItem] = [ - { - let item = ImportQueueItem(url: .init(fileURLWithPath: "test.bin"), fileType: .unknown) - item.systems = [ - PVSystem(identifier: "com.provenance.jaguar", name: "Jaguar", shortName: "Jag", manufacturer: "Atari", screenType: .crt), - PVSystem(identifier: "com.provenance.jaguarcd", name: "Jaguar CD", shortName: "JagCD", manufacturer: "Atari", screenType: .crt) - ] - return item - }(), - ImportQueueItem(url: .init(fileURLWithPath: "test.jpg"), fileType: .artwork), - ImportQueueItem(url: .init(fileURLWithPath: "bios.bin"), fileType: .bios), - ImportQueueItem(url: .init(fileURLWithPath: "test.cue"), fileType: .cdRom), - ImportQueueItem(url: .init(fileURLWithPath: "test.jag"), fileType: .game) - ] - @Published private(set) var processingState: ProcessingState = .idle + @Published private(set) public var importStatus: String = "Ready" + @Published public var importQueue: [ImportQueueItem] = [] + @Published private(set) public var processingState: ProcessingState = .idle - func initSystems() async { + public init(importStatus: String = "", importQueue: [ImportQueueItem] = [], processingState: ProcessingState = .idle, importStartedHandler: GameImporterImportStartedHandler? = nil, completionHandler: GameImporterCompletionHandler? = nil, finishedImportHandler: GameImporterFinishedImportingGameHandler? = nil, finishedArtworkHandler: GameImporterFinishedGettingArtworkHandler? = nil, spotlightCompletionHandler: GameImporterCompletionHandler? = nil, spotlightFinishedImportHandler: GameImporterFinishedImportingGameHandler? = nil) { + self.importStatus = importStatus + self.importQueue = importQueue + self.processingState = processingState + self.importStartedHandler = importStartedHandler + self.completionHandler = completionHandler + self.finishedImportHandler = finishedImportHandler + self.finishedArtworkHandler = finishedArtworkHandler + self.spotlightCompletionHandler = spotlightCompletionHandler + self.spotlightFinishedImportHandler = spotlightFinishedImportHandler + + if self.importQueue.isEmpty { + self.importQueue = [{ + let item = ImportQueueItem(url: .init(fileURLWithPath: "test.bin"), fileType: .unknown) + item.systems = [ + PVSystem(identifier: "com.provenance.jaguar", name: "Jaguar", shortName: "Jag", manufacturer: "Atari", screenType: .crt), + PVSystem(identifier: "com.provenance.jaguarcd", name: "Jaguar CD", shortName: "Jag", manufacturer: "Atari", screenType: .crt) + ] + return item + }(), + ImportQueueItem(url: .init(fileURLWithPath: "test.jpg"), fileType: .artwork), + ImportQueueItem(url: .init(fileURLWithPath: "bios.bin"), fileType: .bios), + ImportQueueItem(url: .init(fileURLWithPath: "test.cue"), fileType: .cdRom), + ImportQueueItem(url: .init(fileURLWithPath: "test.jag"), fileType: .game) + ] + } + } + + public func initSystems() async { // Mock implementation - no real systems to initialize importStatus = "Systems initialized" } - func addImport(_ item: ImportQueueItem) { + public func addImport(_ item: ImportQueueItem) { if !importQueueContainsDuplicate(importQueue, ofItem: item) { importQueue.append(item) importStatus = "Added \(item.url.lastPathComponent) to queue" } } - func addImports(forPaths paths: [URL], targetSystem: AnySystem) { + public func addImports(forPaths paths: [URL], targetSystem: AnySystem) { for path in paths { let item = ImportQueueItem(url: path, fileType: .unknown) item.userChosenSystem = targetSystem as? PVSystem // TODO: change when generic system types supported @@ -50,7 +66,7 @@ class MockGameImporter: GameImporting, ObservableObject { } - func addImports(forPaths paths: [URL]) { + public func addImports(forPaths paths: [URL]) { for path in paths { let item = ImportQueueItem(url: path, fileType: .unknown) addImport(item) @@ -58,12 +74,12 @@ class MockGameImporter: GameImporting, ObservableObject { importStatus = "Added \(paths.count) items to queue" } - func removeImports(at offsets: IndexSet) { + public func removeImports(at offsets: IndexSet) { importQueue.remove(atOffsets: offsets) importStatus = "Removed \(offsets.count) items from queue" } - func startProcessing() { + public func startProcessing() { guard !importQueue.isEmpty else { importStatus = "No items in queue" return @@ -87,26 +103,26 @@ class MockGameImporter: GameImporting, ObservableObject { } } - func sortImportQueueItems(_ importQueueItems: [ImportQueueItem]) -> [ImportQueueItem] { + public func sortImportQueueItems(_ importQueueItems: [ImportQueueItem]) -> [ImportQueueItem] { importQueueItems.sorted(by: { $0.url.lastPathComponent < $1.url.lastPathComponent }) } - func importQueueContainsDuplicate(_ queue: [ImportQueueItem], ofItem queueItem: ImportQueueItem) -> Bool { + public func importQueueContainsDuplicate(_ queue: [ImportQueueItem], ofItem queueItem: ImportQueueItem) -> Bool { queue.contains(where: { $0.url == queueItem.url }) } - var importStartedHandler: GameImporterImportStartedHandler? = nil + public var importStartedHandler: GameImporterImportStartedHandler? = nil /// Closure called when import completes - var completionHandler: GameImporterCompletionHandler? = nil + public var completionHandler: GameImporterCompletionHandler? = nil /// Closure called when a game finishes importing - var finishedImportHandler: GameImporterFinishedImportingGameHandler? = nil + public var finishedImportHandler: GameImporterFinishedImportingGameHandler? = nil /// Closure called when artwork finishes downloading - var finishedArtworkHandler: GameImporterFinishedGettingArtworkHandler? = nil + public var finishedArtworkHandler: GameImporterFinishedGettingArtworkHandler? = nil /// Spotlight Handerls /// Closure called when spotlight completes - var spotlightCompletionHandler: GameImporterCompletionHandler? = nil + public var spotlightCompletionHandler: GameImporterCompletionHandler? = nil /// Closure called when a game finishes importing - var spotlightFinishedImportHandler: GameImporterFinishedImportingGameHandler? = nil + public var spotlightFinishedImportHandler: GameImporterFinishedImportingGameHandler? = nil } diff --git a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockImportStatusDelegate.swift b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockImportStatusDelegate.swift index a347c866fc..19c446416c 100644 --- a/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockImportStatusDelegate.swift +++ b/PVUI/Sources/PVSwiftUI/Imports/ImportStatusTesting/MockImportStatusDelegate.swift @@ -7,26 +7,26 @@ import PVSwiftUI -class MockImportStatusDriverData: ObservableObject { +public class MockImportStatusDriverData: ObservableObject { @MainActor - let gameImporter = MockGameImporter() //AppState.shared.gameImporter ?? GameImporter.shared + public let gameImporter = MockGameImporter() //AppState.shared.gameImporter ?? GameImporter.shared @MainActor - let pvgamelibraryUpdatesController: PVGameLibraryUpdatesController + public let pvgamelibraryUpdatesController: PVGameLibraryUpdatesController - var isPresent: Bool = false + public var isPresent: Bool = false @MainActor - init() { + public init() { pvgamelibraryUpdatesController = .init(gameImporter: gameImporter) } } extension MockImportStatusDriverData: ImportStatusDelegate { - func dismissAction() { + public func dismissAction() { } - func addImportsAction() { + public func addImportsAction() { let importItems: [ImportQueueItem] = [ .init(url: .init(string: "test.jag")!, fileType: .unknown) ] @@ -35,7 +35,7 @@ extension MockImportStatusDriverData: ImportStatusDelegate { } } - func forceImportsAction() { + public func forceImportsAction() { gameImporter.startProcessing() } } diff --git a/UITesting/UITesting/Imports/ImportStatusView.swift b/UITesting/UITesting/Imports/ImportStatusView.swift deleted file mode 100644 index 18061f76a8..0000000000 --- a/UITesting/UITesting/Imports/ImportStatusView.swift +++ /dev/null @@ -1,122 +0,0 @@ - -// -// ImportStatusView.swift -// PVUI -// -// Created by David Proskin on 10/31/24. -// - -import SwiftUI -import PVUIBase -import PVLibrary -import PVThemes -import Perception - -public protocol ImportStatusDelegate : AnyObject { - func dismissAction() - func addImportsAction() - func forceImportsAction() -} - -func iconNameForFileType(_ type: FileType) -> String { - - switch type { - case .bios: - return "bios_filled" - case .artwork: - return "image_icon_256" - case .game: - return "file_icon_256" - case .cdRom: - return "cd_icon_256" - case .unknown: - return "questionMark" - } -} - -public struct ImportStatusView: View { - @ObservedObject - public var updatesController: PVGameLibraryUpdatesController - public var gameImporter:any GameImporting - public weak var delegate:ImportStatusDelegate! - public var dismissAction: (() -> Void)? = nil - - @ObservedObject private var themeManager = ThemeManager.shared - var currentPalette: any UXThemePalette { themeManager.currentPalette } - - public init(updatesController: PVGameLibraryUpdatesController, gameImporter: any GameImporting, delegate: ImportStatusDelegate, dismissAction: (() -> Void)? = nil) { - self.updatesController = updatesController - self.gameImporter = gameImporter - self.delegate = delegate - self.dismissAction = dismissAction - } - - private func deleteItems(at offsets: IndexSet) { - gameImporter.removeImports(at: offsets) - } - - public var body: some View { - WithPerceptionTracking { - NavigationView { - List { - if gameImporter.importQueue.isEmpty { - Text("No items in the import queue") - .foregroundColor(.secondary) - .padding() - } else { - ForEach(gameImporter.importQueue) { item in - ImportTaskRowView(item: item).id(item.id) - }.onDelete( - perform: deleteItems - ) - } - } - .navigationTitle("Import Queue") - .toolbar { - ToolbarItemGroup(placement: .topBarLeading, - content: { - if dismissAction != nil { - Button("Done") { delegate.dismissAction() - } - .tint(currentPalette.defaultTintColor?.swiftUIColor) - } - }) - ToolbarItemGroup(placement: .topBarTrailing, - content: { - Button("Add Files") { - delegate?.addImportsAction() - } - .tint(currentPalette.defaultTintColor?.swiftUIColor) - Button("Begin") { - delegate?.forceImportsAction() - } - .tint(currentPalette.defaultTintColor?.swiftUIColor) - }) - } - .background(currentPalette.gameLibraryBackground.swiftUIColor) - } - .background(currentPalette.gameLibraryBackground.swiftUIColor) - .presentationDetents([.medium, .large]) - .presentationDragIndicator(.visible) - } - } -} - -#if DEBUG -#Preview { - @ObservedObject var themeManager = ThemeManager.shared - var currentPalette: any UXThemePalette { themeManager.currentPalette } - - let mockImportStatusDriverData = MockImportStatusDriverData() - - ImportStatusView( - updatesController: mockImportStatusDriverData.pvgamelibraryUpdatesController, - gameImporter: mockImportStatusDriverData.gameImporter, - delegate: mockImportStatusDriverData) { - print("Import Status View Closed") - } - .onAppear { - themeManager.setCurrentPalette(CGAThemes.green.palette) - } -} -#endif diff --git a/UITesting/UITesting/Imports/ImportTaskRowView.swift b/UITesting/UITesting/Imports/ImportTaskRowView.swift deleted file mode 100644 index e0c9fa80ff..0000000000 --- a/UITesting/UITesting/Imports/ImportTaskRowView.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// ImportTaskRowView.swift -// PVUI -// -// Created by Joseph Mattiello on 11/17/24. -// - -import SwiftUI -import PVLibrary -import PVThemes -import Perception - -func iconNameForStatus(_ status: ImportStatus) -> String { - switch status { - - case .queued: - return "line.3.horizontal.circle" - case .processing: - return "progress.indicator" - case .success: - return "checkmark.circle.fill" - case .failure: - return "xmark.diamond.fill" - case .conflict: - return "exclamationmark.triangle.fill" - case .partial: - return "display.trianglebadge.exclamationmark" - } -} - -// Individual Import Task Row View -struct ImportTaskRowView: View { - let item: ImportQueueItem - @State private var isNavigatingToSystemSelection = false - @ObservedObject private var themeManager = ThemeManager.shared - var currentPalette: any UXThemePalette { themeManager.currentPalette } - - var bgColor: Color { - themeManager.currentPalette.settingsCellBackground?.swiftUIColor ?? themeManager.currentPalette.menuBackground.swiftUIColor - } - - var fgColor: Color { - themeManager.currentPalette.settingsCellText?.swiftUIColor ?? themeManager.currentPalette.menuText.swiftUIColor - } - - var secondaryFgColor: Color { - themeManager.currentPalette.settingsCellTextDetail?.swiftUIColor ?? .secondary - } - - var body: some View { - WithPerceptionTracking { - HStack { -// // TODO: add icon for fileType - VStack(alignment: .leading) { - Text(item.url.lastPathComponent) - .font(.headline) - .foregroundColor(fgColor) - - if item.fileType == .bios { - Text("BIOS") - .font(.subheadline) - .foregroundColor(fgColor) - } else if !item.systems.isEmpty { - Text("\(item.systems.count) systems") - .font(.subheadline) - .foregroundColor(secondaryFgColor) - } - - if item.status == .failure, let errorText = item.errorValue { - Text("Error Detail: \(errorText)") - .font(.subheadline) - .foregroundColor(secondaryFgColor) - } - } - - Spacer() - - VStack(alignment: .trailing) { - if item.status == .processing { - ProgressView() - .progressViewStyle(.circular) - .frame(width: 40, height: 40, alignment: .center) -// .tint(themeManager.currentPalette.defaultTintColor!) - } else { - Image(systemName: iconNameForStatus(item.status)) - .foregroundColor(item.status.color) - } - - if (item.childQueueItems.count > 0) { - Text("+\(item.childQueueItems.count) files") - .font(.subheadline) - .foregroundColor(fgColor) - } - } - - } - .padding() - .background( - RoundedRectangle(cornerRadius: 10) - .fill(bgColor) - .shadow(radius: 2) - ) - //.background(currentPalette.gameLibraryBackground.swiftUIColor) - .onTapGesture { - if item.status == .conflict { - isNavigatingToSystemSelection = true - } - } - .background( - NavigationLink(destination: SystemSelectionView(item: item), isActive: $isNavigatingToSystemSelection) { - EmptyView() - } - .hidden() - ) - } - } -} -#if DEBUG -import PVThemes -#Preview { - List { - ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.jpg"), fileType: .artwork)) - ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.bin"), fileType: .bios)) - ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.iso"), fileType: .cdRom)) - ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.jag"), fileType: .game)) - ImportTaskRowView(item: .init(url: .init(fileURLWithPath: "Test.zip"), fileType: .unknown)) - } - .onAppear { - ThemeManager.shared.setCurrentPalette(CGAThemes.green.palette) - } -} -#endif diff --git a/UITesting/UITesting/Imports/SystemSelectionView.swift b/UITesting/UITesting/Imports/SystemSelectionView.swift deleted file mode 100644 index bacfbd6689..0000000000 --- a/UITesting/UITesting/Imports/SystemSelectionView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// SystemSelectionView.swift -// PVUI -// -// Created by David Proskin on 11/7/24. -// - -import SwiftUI -import PVLibrary - -struct SystemSelectionView: View { - @ObservedObject var item: ImportQueueItem - @Environment(\.presentationMode) var presentationMode - - var body: some View { - List { - ForEach(item.systems.sorted(by: { a, b in - return a.name <= b.name - }), id: \.self) { system in - Button(action: { - // Set the chosen system and update the status - item.userChosenSystem = system - item.status = .queued - // Dismiss the view - presentationMode.wrappedValue.dismiss() - }) { - Text(system.name) - .font(.headline) - .padding() - } - } - } - .navigationTitle("Select System") - } -} - -#if DEBUG -import PVPrimitives -#Preview { - - - let item: ImportQueueItem = { - let systems: [PVSystem] = [ - .init( - identifier: "com.provenance.jaguar", - name: "Jaguar", - shortName: "Jag", - manufacturer: "Atari", - screenType: .crt - ), - .init( - identifier: "com.provenance.jaguarcd", - name: "Jaguar CD", - shortName: "Jag CD", - manufacturer: "Atari", - screenType: .crt - ) - ] - - let item = ImportQueueItem(url: .init(fileURLWithPath: "Test.bin"), - fileType: .game) - item.systems = systems - return item - }() - - List { - SystemSelectionView(item: item) - } -} -#endif