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 + + +