forked from MeloNX/MeloNX
Add games, delete games and game info view
This commit is contained in:
parent
9c014e6f87
commit
a2c3f6d624
@ -618,7 +618,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 4TD3JXVDW7;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = NO;
|
ENABLE_TESTABILITY = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@ -626,6 +626,8 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
|
"$(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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = fast;
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -633,11 +635,13 @@
|
|||||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
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_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UIRequiresFullScreen = 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;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.0.8;
|
MARKETING_VERSION = 0.0.8;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
||||||
@ -671,7 +679,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 4TD3JXVDW7;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@ -679,6 +687,8 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
|
"$(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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = fast;
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -686,11 +696,13 @@
|
|||||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
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_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UIRequiresFullScreen = 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;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.0.8;
|
MARKETING_VERSION = 0.0.8;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
||||||
|
Binary file not shown.
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -13,7 +13,6 @@ public struct Game: Identifiable, Equatable {
|
|||||||
|
|
||||||
var containerFolder: URL
|
var containerFolder: URL
|
||||||
var fileType: UTType
|
var fileType: UTType
|
||||||
|
|
||||||
var fileURL: URL
|
var fileURL: URL
|
||||||
|
|
||||||
var titleName: String
|
var titleName: String
|
||||||
@ -59,12 +58,8 @@ public struct Game: Identifiable, Equatable {
|
|||||||
gameTemp.icon = UIImage(data: imageData)
|
gameTemp.icon = UIImage(data: imageData)
|
||||||
} else {
|
} else {
|
||||||
print("Invalid image size.")
|
print("Invalid image size.")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return gameTemp
|
return gameTemp
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createImage(from gameInfo: GameInfo) -> UIImage? {
|
func createImage(from gameInfo: GameInfo) -> UIImage? {
|
||||||
@ -82,7 +77,6 @@ public struct Game: Identifiable, Equatable {
|
|||||||
let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
|
let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
|
||||||
|
|
||||||
// Create a UIImage (or NSImage on macOS)
|
// Create a UIImage (or NSImage on macOS)
|
||||||
|
|
||||||
print(imageData)
|
print(imageData)
|
||||||
|
|
||||||
return UIImage(data: imageData)
|
return UIImage(data: imageData)
|
||||||
|
104
src/MeloNX/MeloNX/App/Views/GamesList/GameInfoSheet.swift
Normal file
104
src/MeloNX/MeloNX/App/Views/GamesList/GameInfoSheet.swift
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,10 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UniformTypeIdentifiers
|
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 {
|
struct GameLibraryView: View {
|
||||||
@Binding var startemu: Game?
|
@Binding var startemu: Game?
|
||||||
@ -22,6 +26,9 @@ struct GameLibraryView: View {
|
|||||||
@State var firmwareversion = "0"
|
@State var firmwareversion = "0"
|
||||||
@State var isImporting: Bool = false
|
@State var isImporting: Bool = false
|
||||||
@State var startgame = false
|
@State var startgame = false
|
||||||
|
@State var isSelectingGameFile = false
|
||||||
|
@State var isViewingGameInfo: Bool = false
|
||||||
|
@State var gameInfo: Game?
|
||||||
|
|
||||||
|
|
||||||
var filteredGames: [Game] {
|
var filteredGames: [Game] {
|
||||||
@ -88,7 +95,7 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
LazyVStack(spacing: 2) {
|
LazyVStack(spacing: 2) {
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu)
|
GameListRow(game: game, startemu: $startemu, games: $games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
addToRecentGames(game)
|
addToRecentGames(game)
|
||||||
}
|
}
|
||||||
@ -98,7 +105,7 @@ struct GameLibraryView: View {
|
|||||||
} else {
|
} else {
|
||||||
LazyVStack(spacing: 2) {
|
LazyVStack(spacing: 2) {
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu)
|
GameListRow(game: game, startemu: $startemu, games: $games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
addToRecentGames(game)
|
addToRecentGames(game)
|
||||||
}
|
}
|
||||||
@ -111,17 +118,13 @@ struct GameLibraryView: View {
|
|||||||
loadGames()
|
loadGames()
|
||||||
loadRecentGames()
|
loadRecentGames()
|
||||||
|
|
||||||
|
|
||||||
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
||||||
firmwareversion = (firmware == "" ? "0" : firmware)
|
firmwareversion = (firmware == "" ? "0" : firmware)
|
||||||
}
|
}
|
||||||
.fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in
|
.fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in
|
||||||
switch result {
|
switch result {
|
||||||
|
|
||||||
case .success(let url):
|
case .success(let url):
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
let fun = url.startAccessingSecurityScopedResource()
|
let fun = url.startAccessingSecurityScopedResource()
|
||||||
let path = url.path
|
let path = url.path
|
||||||
|
|
||||||
@ -132,16 +135,22 @@ struct GameLibraryView: View {
|
|||||||
url.stopAccessingSecurityScopedResource()
|
url.stopAccessingSecurityScopedResource()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
|
Button {
|
||||||
|
isSelectingGameFile.toggle()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "plus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Menu {
|
Menu {
|
||||||
|
|
||||||
Text("Firmware Version: \(firmwareversion)")
|
Text("Firmware Version: \(firmwareversion)")
|
||||||
.tint(.white)
|
.tint(.white)
|
||||||
|
|
||||||
@ -164,7 +173,6 @@ struct GameLibraryView: View {
|
|||||||
Text("Remove Firmware")
|
Text("Remove Firmware")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
let game = Game(containerFolder: URL(string: "none")!, fileType: .item, fileURL: URL(string: "MiiMaker")!, titleName: "Mii Maker", titleId: "0", developer: "Nintendo", version: firmwareversion)
|
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 {
|
Button {
|
||||||
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
let sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
|
let sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
|
||||||
@ -198,7 +204,6 @@ struct GameLibraryView: View {
|
|||||||
Image(systemName: "ellipsis.circle")
|
Image(systemName: "ellipsis.circle")
|
||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,14 +236,53 @@ struct GameLibraryView: View {
|
|||||||
} catch {
|
} catch {
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case .failure(let err):
|
case .failure(let err):
|
||||||
print("File import failed: \(err.localizedDescription)")
|
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
|
let fileManager = FileManager.default
|
||||||
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
||||||
|
|
||||||
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
|
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
|
||||||
|
|
||||||
// Check if "roms" folder exists; if not, create it
|
// Check if "roms" folder exists; if not, create it
|
||||||
if !fileManager.fileExists(atPath: romsDirectory.path) {
|
if (!fileManager.fileExists(atPath: romsDirectory.path)) {
|
||||||
do {
|
do {
|
||||||
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
} catch {
|
} catch {
|
||||||
@ -314,8 +359,21 @@ struct GameLibraryView: View {
|
|||||||
print("Error loading games from roms folder: \(error)")
|
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 {
|
extension Game: Codable {
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case titleName, titleId, developer, version, fileURL
|
case titleName, titleId, developer, version, fileURL
|
||||||
@ -344,6 +402,7 @@ extension Game: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -Recent Game Card
|
||||||
struct RecentGameCard: View {
|
struct RecentGameCard: View {
|
||||||
let game: Game
|
let game: Game
|
||||||
@Binding var startemu: Game?
|
@Binding var startemu: Game?
|
||||||
@ -390,9 +449,15 @@ struct RecentGameCard: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -Game List Item
|
||||||
struct GameListRow: View {
|
struct GameListRow: View {
|
||||||
let game: Game
|
let game: Game
|
||||||
@Binding var startemu: 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
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -442,20 +507,52 @@ struct GameListRow: View {
|
|||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
.background(Color(.systemBackground))
|
.background(Color(.systemBackground))
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button {
|
Section {
|
||||||
startemu = game
|
Button {
|
||||||
} label: {
|
startemu = game
|
||||||
Label("Play Now", systemImage: "play.fill")
|
} label: {
|
||||||
|
Label("Play Now", systemImage: "play.fill")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
gameInfo = game
|
||||||
|
isViewingGameInfo.toggle()
|
||||||
|
} label: {
|
||||||
|
Label("Game Info", systemImage: "info.circle")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Section {
|
||||||
let pasteboard = UIPasteboard.general
|
Button(role: .destructive) {
|
||||||
pasteboard.string = game.titleId
|
gametoDelete = game
|
||||||
} label: {
|
showGameDeleteConfirmation.toggle()
|
||||||
Label("Game ID: \(game.titleId)", systemImage: "info.circle")
|
} label: {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.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)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,5 +6,48 @@
|
|||||||
<string>1d0e26921bac938456ee7210ff4f2fa701dc16c02de1760e0aa757db28818ec7</string>
|
<string>1d0e26921bac938456ee7210ff4f2fa701dc16c02de1760e0aa757db28818ec7</string>
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UTExportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.nintendo.switch-package</string>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Nintendo Switch Package</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.archive</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>nsp</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<string>application/x-nsp</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.nintendo.switch-cartridge</string>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Nintendo Switch Cartridge</string>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.archive</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>xci</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<string>application/x-xci</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user