From a2c3f6d624037877b90ddc94696fe1bfd2787c34 Mon Sep 17 00:00:00 2001 From: Bella <65065992+Bentheminernz@users.noreply.github.com> Date: Sun, 9 Feb 2025 00:15:37 +1300 Subject: [PATCH] Add games, delete games and game info view --- src/MeloNX/MeloNX.xcodeproj/project.pbxproj | 28 +++- .../UserInterfaceState.xcuserstate | Bin 0 -> 29397 bytes .../xcschemes/xcschememanagement.plist | 24 +++ src/MeloNX/MeloNX/App/Models/Game.swift | 6 - .../App/Views/GamesList/GameInfoSheet.swift | 104 ++++++++++++ .../App/Views/GamesList/GameListView.swift | 149 +++++++++++++++--- src/MeloNX/MeloNX/Info.plist | 43 +++++ 7 files changed, 316 insertions(+), 38 deletions(-) create mode 100644 src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/benlawrence.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 src/MeloNX/MeloNX.xcodeproj/xcuserdata/benlawrence.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 src/MeloNX/MeloNX/App/Views/GamesList/GameInfoSheet.swift diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj index 7b86e1b25..012e89633 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj +++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj @@ -618,7 +618,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 95J8WZ4TN8; + DEVELOPMENT_TEAM = 4TD3JXVDW7; ENABLE_PREVIEWS = YES; ENABLE_TESTABILITY = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -626,6 +626,8 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); GCC_OPTIMIZATION_LEVEL = fast; GENERATE_INFOPLIST_FILE = YES; @@ -633,11 +635,13 @@ INFOPLIST_KEY_GCSupportsGameMode = YES; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "MeloNX needs access to your Photo Library in order to save images"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UIRequiresFullScreen = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportsDocumentBrowser = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -651,9 +655,13 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = 0.0.8; - PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; + PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h"; @@ -671,7 +679,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 95J8WZ4TN8; + DEVELOPMENT_TEAM = 4TD3JXVDW7; ENABLE_PREVIEWS = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -679,6 +687,8 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); GCC_OPTIMIZATION_LEVEL = fast; GENERATE_INFOPLIST_FILE = YES; @@ -686,11 +696,13 @@ INFOPLIST_KEY_GCSupportsGameMode = YES; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games"; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "MeloNX needs access to your Photo Library in order to save images"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UIRequiresFullScreen = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportsDocumentBrowser = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -704,9 +716,13 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = 0.0.8; - PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; + PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h"; diff --git a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/benlawrence.xcuserdatad/UserInterfaceState.xcuserstate b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/benlawrence.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..d95c76d6512f8a9579497e23d38ec26472381107 GIT binary patch literal 29397 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 literal 0 HcmV?d00001 diff --git a/src/MeloNX/MeloNX.xcodeproj/xcuserdata/benlawrence.xcuserdatad/xcschemes/xcschememanagement.plist b/src/MeloNX/MeloNX.xcodeproj/xcuserdata/benlawrence.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..1b8118340 --- /dev/null +++ b/src/MeloNX/MeloNX.xcodeproj/xcuserdata/benlawrence.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + MeloNX.xcscheme_^#shared#^_ + + orderHint + 0 + + Ryujinx.xcscheme_^#shared#^_ + + orderHint + 1 + + com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_ + + orderHint + 2 + + + + diff --git a/src/MeloNX/MeloNX/App/Models/Game.swift b/src/MeloNX/MeloNX/App/Models/Game.swift index de6acc5c0..ee13dd3f6 100644 --- a/src/MeloNX/MeloNX/App/Models/Game.swift +++ b/src/MeloNX/MeloNX/App/Models/Game.swift @@ -13,7 +13,6 @@ public struct Game: Identifiable, Equatable { var containerFolder: URL var fileType: UTType - var fileURL: URL var titleName: String @@ -59,12 +58,8 @@ public struct Game: Identifiable, Equatable { gameTemp.icon = UIImage(data: imageData) } else { print("Invalid image size.") - } - - return gameTemp - } func createImage(from gameInfo: GameInfo) -> UIImage? { @@ -82,7 +77,6 @@ public struct Game: Identifiable, Equatable { let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize) // Create a UIImage (or NSImage on macOS) - print(imageData) return UIImage(data: imageData) diff --git a/src/MeloNX/MeloNX/App/Views/GamesList/GameInfoSheet.swift b/src/MeloNX/MeloNX/App/Views/GamesList/GameInfoSheet.swift new file mode 100644 index 000000000..45dea7551 --- /dev/null +++ b/src/MeloNX/MeloNX/App/Views/GamesList/GameInfoSheet.swift @@ -0,0 +1,104 @@ +// +// GameInfoSheet.swift +// MeloNX +// +// Created by Bella on 08/02/2025. +// + +import SwiftUI + +struct GameInfoSheet: View { + let game: Game + + @Environment(\.dismiss) var dismiss + + var body: some View { + iOSNav { + VStack { + if let icon = game.icon { + Image(uiImage: icon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 250, height: 250) + .cornerRadius(10) + .padding() + .contextMenu { + Button { + UIImageWriteToSavedPhotosAlbum(icon, nil, nil, nil) + } label: { + Label("Save to Photos", systemImage: "square.and.arrow.down") + } + } + } else { + Image(systemName: "questionmark.circle") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 150, height: 150) + .padding() + } + + VStack(alignment: .leading) { + VStack(alignment: .leading) { + Text("**\(game.titleName)** | \(game.titleId.capitalized)") + Text(game.developer) + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(.vertical, 3) + + VStack(alignment: .leading, spacing: 5) { + Text("Information") + .font(.title2) + .bold() + + Text("**Version:** \(game.version)") + Text("**Game Size:** \(fetchFileSize(for: game.fileURL) ?? 0) bytes") + Text("**File Type:** .\(getFileType(game.fileURL))") + Text("**Game URL:** \(trimGameURL(game.fileURL))") + } + } + + Spacer() + } + .padding(.horizontal, 5) + .navigationTitle(game.titleName) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + dismiss() + } + } + } + } + } + + func fetchFileSize(for gamePath: URL) -> UInt64? { + let fileManager = FileManager.default + do { + let attributes = try fileManager.attributesOfItem(atPath: gamePath.path) + if let size = attributes[FileAttributeKey.size] as? UInt64 { + return size + } + } catch { + print("Error getting file size: \(error)") + } + return nil + } + + func trimGameURL(_ url: URL) -> String { + let path = url.path + if let range = path.range(of: "/roms/") { + return String(path[range.lowerBound...]) + } + return path + } + + func getFileType(_ url: URL) -> String { + let path = url.path + if let range = path.range(of: ".") { + return String(path[range.upperBound...]) + } + return "Unknown" + } +} diff --git a/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift b/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift index 8aac6be9a..6ab186c0d 100644 --- a/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift +++ b/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift @@ -8,6 +8,10 @@ import SwiftUI import UniformTypeIdentifiers +extension UTType { + static let nsp = UTType(exportedAs: "com.nintendo.switch-package") + static let xci = UTType(exportedAs: "com.nintendo.switch-cartridge") +} struct GameLibraryView: View { @Binding var startemu: Game? @@ -22,6 +26,9 @@ struct GameLibraryView: View { @State var firmwareversion = "0" @State var isImporting: Bool = false @State var startgame = false + @State var isSelectingGameFile = false + @State var isViewingGameInfo: Bool = false + @State var gameInfo: Game? var filteredGames: [Game] { @@ -88,7 +95,7 @@ struct GameLibraryView: View { LazyVStack(spacing: 2) { ForEach(filteredGames) { game in - GameListRow(game: game, startemu: $startemu) + GameListRow(game: game, startemu: $startemu, games: $games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo) .onTapGesture { addToRecentGames(game) } @@ -98,7 +105,7 @@ struct GameLibraryView: View { } else { LazyVStack(spacing: 2) { ForEach(filteredGames) { game in - GameListRow(game: game, startemu: $startemu) + GameListRow(game: game, startemu: $startemu, games: $games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo) .onTapGesture { addToRecentGames(game) } @@ -111,17 +118,13 @@ struct GameLibraryView: View { loadGames() loadRecentGames() - let firmware = Ryujinx.shared.fetchFirmwareVersion() firmwareversion = (firmware == "" ? "0" : firmware) } .fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in switch result { - case .success(let url): - do { - let fun = url.startAccessingSecurityScopedResource() let path = url.path @@ -132,16 +135,22 @@ struct GameLibraryView: View { url.stopAccessingSecurityScopedResource() } } - case .failure(let error): print(error) } } } .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button { + isSelectingGameFile.toggle() + } label: { + Image(systemName: "plus") + } + } + ToolbarItem(placement: .topBarLeading) { Menu { - Text("Firmware Version: \(firmwareversion)") .tint(.white) @@ -164,7 +173,6 @@ struct GameLibraryView: View { Text("Remove Firmware") } - Button { let game = Game(containerFolder: URL(string: "none")!, fileType: .item, fileURL: URL(string: "MiiMaker")!, titleName: "Mii Maker", titleId: "0", developer: "Nintendo", version: firmwareversion) @@ -182,8 +190,6 @@ struct GameLibraryView: View { } } - - Button { let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://") @@ -198,7 +204,6 @@ struct GameLibraryView: View { Image(systemName: "ellipsis.circle") .foregroundColor(.blue) } - } } } @@ -231,14 +236,53 @@ struct GameLibraryView: View { } catch { print(error) } - case .failure(let err): print("File import failed: \(err.localizedDescription)") } } - - + .fileImporter(isPresented: $isSelectingGameFile, allowedContentTypes: [.nsp, .xci, .zip, .folder]) { result in + switch result { + case .success(let url): + guard url.startAccessingSecurityScopedResource() else { + print("Failed to access security-scoped resource") + return + } + defer { url.stopAccessingSecurityScopedResource() } + + do { + let fileManager = FileManager.default + let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! + let romsDirectory = documentsDirectory.appendingPathComponent("roms") + + if !fileManager.fileExists(atPath: romsDirectory.path) { + try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil) + } + + let destinationURL = romsDirectory.appendingPathComponent(url.lastPathComponent) + try fileManager.copyItem(at: url, to: destinationURL) + + loadGames() + } catch { + print("Error copying game file: \(error)") + } + case .failure(let err): + print("File import failed: \(err.localizedDescription)") + } + } + .sheet(isPresented: Binding( + get: { isViewingGameInfo && gameInfo != nil }, + set: { newValue in + if !newValue { + isViewingGameInfo = false + gameInfo = nil + } + } + )) { + if let game = gameInfo { + GameInfoSheet(game: game) + } + } } @@ -274,14 +318,15 @@ struct GameLibraryView: View { } } - private func loadGames() { +// MARK: - loads games from roms + func loadGames() { let fileManager = FileManager.default guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return } let romsDirectory = documentsDirectory.appendingPathComponent("roms") // Check if "roms" folder exists; if not, create it - if !fileManager.fileExists(atPath: romsDirectory.path) { + if (!fileManager.fileExists(atPath: romsDirectory.path)) { do { try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil) } catch { @@ -314,8 +359,21 @@ struct GameLibraryView: View { print("Error loading games from roms folder: \(error)") } } + +// MARK: - Delete Game Function + func deleteGame(game: Game) { + let fileManager = FileManager.default + do { + try fileManager.removeItem(at: game.fileURL) + games.removeAll { $0.id == game.id } + loadGames() + } catch { + print("Error deleting game: \(error)") + } + } } +// MARK: -Game Model extension Game: Codable { enum CodingKeys: String, CodingKey { case titleName, titleId, developer, version, fileURL @@ -344,6 +402,7 @@ extension Game: Codable { } } +// MARK: -Recent Game Card struct RecentGameCard: View { let game: Game @Binding var startemu: Game? @@ -390,9 +449,15 @@ struct RecentGameCard: View { } } +// MARK: -Game List Item struct GameListRow: View { let game: Game @Binding var startemu: Game? + @Binding var games: [Game] // Add this binding + @Binding var isViewingGameInfo: Bool + @Binding var gameInfo: Game? + @State var gametoDelete: Game? + @State var showGameDeleteConfirmation: Bool = false @Environment(\.colorScheme) var colorScheme var body: some View { @@ -442,20 +507,52 @@ struct GameListRow: View { .padding(.vertical, 8) .background(Color(.systemBackground)) .contextMenu { - Button { - startemu = game - } label: { - Label("Play Now", systemImage: "play.fill") + Section { + Button { + startemu = game + } label: { + Label("Play Now", systemImage: "play.fill") + } + + Button { + gameInfo = game + isViewingGameInfo.toggle() + } label: { + Label("Game Info", systemImage: "info.circle") + } } - Button { - let pasteboard = UIPasteboard.general - pasteboard.string = game.titleId - } label: { - Label("Game ID: \(game.titleId)", systemImage: "info.circle") + Section { + Button(role: .destructive) { + gametoDelete = game + showGameDeleteConfirmation.toggle() + } label: { + Label("Delete", systemImage: "trash") + } } } } .buttonStyle(.plain) + .confirmationDialog("Are you sure you want to delete this game?", isPresented: $showGameDeleteConfirmation) { + Button("Delete", role: .destructive) { + if let game = gametoDelete { + deleteGame(game: game) + } + } + Button("Cancel", role: .cancel) {} + } message: { + Text("Are you sure you want to delete \(gametoDelete?.titleName ?? "this game")?") + } + } + + private func deleteGame(game: Game) { + let fileManager = FileManager.default + do { + try fileManager.removeItem(at: game.fileURL) + games.removeAll { $0.id == game.id } + } catch { + print("Error deleting game: \(error)") + } } } + diff --git a/src/MeloNX/MeloNX/Info.plist b/src/MeloNX/MeloNX/Info.plist index 1a90a55eb..2080cee6f 100644 --- a/src/MeloNX/MeloNX/Info.plist +++ b/src/MeloNX/MeloNX/Info.plist @@ -6,5 +6,48 @@ 1d0e26921bac938456ee7210ff4f2fa701dc16c02de1760e0aa757db28818ec7 UIFileSharingEnabled + UTExportedTypeDeclarations + + + UTTypeIdentifier + com.nintendo.switch-package + UTTypeDescription + Nintendo Switch Package + UTTypeConformsTo + + public.data + public.archive + + UTTypeTagSpecification + + public.filename-extension + + nsp + + public.mime-type + application/x-nsp + + + + UTTypeIdentifier + com.nintendo.switch-cartridge + UTTypeDescription + Nintendo Switch Cartridge + UTTypeConformsTo + + public.data + public.archive + + UTTypeTagSpecification + + public.filename-extension + + xci + + public.mime-type + application/x-xci + + +