diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj index fcccb2b19..567e8252e 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj +++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj @@ -672,6 +672,8 @@ "$(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; GENERATE_INFOPLIST_FILE = YES; @@ -755,8 +757,12 @@ "$(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 = 1.2.0; + MARKETING_VERSION = 1.3.0; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -814,6 +820,8 @@ "$(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; GENERATE_INFOPLIST_FILE = YES; @@ -897,8 +905,12 @@ "$(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 = 1.2.0; + MARKETING_VERSION = 1.3.0; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate index e66740c97..b67489187 100644 Binary files a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate and b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/src/MeloNX/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcschemes/xcschememanagement.plist b/src/MeloNX/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcschemes/xcschememanagement.plist index 62375ba69..fbd8d81c1 100644 --- a/src/MeloNX/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/src/MeloNX/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,12 +12,12 @@ Ryujinx.xcscheme_^#shared#^_ orderHint - 1 + 3 com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_ orderHint - 2 + 4 SuppressBuildableAutocreation diff --git a/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift b/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift index a4a2558ee..bd09f1b74 100644 --- a/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift +++ b/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift @@ -484,23 +484,26 @@ class Ryujinx { func repeatuntilfindLayer() { - DispatchQueue.global(qos: .background).async { + Task { @MainActor in while self.metalLayer == nil { let layer = self.getMetalLayer(nil) - + if layer != nil { DispatchQueue.main.async { self.metalLayer = layer } + self.metalLayer = layer break } - + Thread.sleep(forTimeInterval: 0.1) + try await Task.sleep(nanoseconds: 100_000_000) } } } - + + @MainActor func getMetalLayer(_ window: OpaquePointer?) -> CAMetalLayer? { var window = window if window == nil { diff --git a/src/MeloNX/MeloNX/App/Models/Game.swift b/src/MeloNX/MeloNX/App/Models/Game.swift index d9ed10422..96d27591d 100644 --- a/src/MeloNX/MeloNX/App/Models/Game.swift +++ b/src/MeloNX/MeloNX/App/Models/Game.swift @@ -9,7 +9,7 @@ import SwiftUI import UniformTypeIdentifiers public struct Game: Identifiable, Equatable, Hashable { - public var id = UUID() + public var id: URL { fileURL } var containerFolder: URL var fileType: UTType diff --git a/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift b/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift index 721734ef7..5673ab7ea 100644 --- a/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift +++ b/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift @@ -39,14 +39,23 @@ struct GameLibraryView: View { var filteredGames: [Game] { if searchText.isEmpty { - return Ryujinx.shared.games + return Ryujinx.shared.games.filter { game in + !realRecentGames.contains(where: { $0.fileURL == game.fileURL }) + } } return Ryujinx.shared.games.filter { $0.titleName.localizedCaseInsensitiveContains(searchText) || - $0.developer.localizedCaseInsensitiveContains(searchText) + $0.developer.localizedCaseInsensitiveContains(searchText) } } - + + var realRecentGames: [Game] { + let games = Ryujinx.shared.games + return recentGames.compactMap { recentGame in + games.first(where: { $0.fileURL == recentGame.fileURL }) + } + } + var body: some View { iOSNav { List { @@ -66,46 +75,32 @@ struct GameLibraryView: View { .frame(maxWidth: .infinity) .padding(.top, 40) } else { - if !isSearching && !recentGames.isEmpty { - VStack(alignment: .leading, spacing: 12) { - Text("Recent") - .font(.title2.bold()) - .padding(.horizontal) - - ScrollView(.horizontal, showsIndicators: false) { - LazyHStack(spacing: 16) { - ForEach(recentGames) { game in - RecentGameCard(game: game, startemu: $startemu) - .onTapGesture { - addToRecentGames(game) - startemu = game - } - } - } - .padding(.horizontal) - } - } - - VStack(alignment: .leading, spacing: 12) { - Text("All Games") - .font(.title2.bold()) - .padding(.horizontal) - - LazyVStack(spacing: 2) { - ForEach(filteredGames) { game in - GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo) - .onTapGesture { - addToRecentGames(game) + if !isSearching && !realRecentGames.isEmpty { + Section { + ForEach(realRecentGames) { game in + GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo) + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + Button(role: .destructive) { + removeFromRecentGames(game) + } label: { + Label("Delete", systemImage: "trash") } - } + } } + } header: { + Text("Recent") + } + + Section { + ForEach(filteredGames) { game in + GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo) + } + } header: { + Text("Others") } } else { ForEach(filteredGames) { game in GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo) - .onTapGesture { - addToRecentGames(game) - } } } } @@ -206,8 +201,13 @@ struct GameLibraryView: View { } } } + .onChange(of: startemu) { game in + guard let game else { return } + addToRecentGames(game) + } } .searchable(text: $searchText) + .animation(.easeInOut, value: searchText) .onChange(of: searchText) { _ in isSearching = !searchText.isEmpty } @@ -291,8 +291,8 @@ struct GameLibraryView: View { } private func addToRecentGames(_ game: Game) { - recentGames.removeAll { $0.id == game.id } - + recentGames.removeAll { $0.titleId == game.titleId } + recentGames.insert(game, at: 0) if recentGames.count > 5 { @@ -301,7 +301,12 @@ struct GameLibraryView: View { saveRecentGames() } - + + private func removeFromRecentGames(_ game: Game) { + recentGames.removeAll { $0.titleId == game.titleId } + saveRecentGames() + } + private func saveRecentGames() { do { let encoder = JSONEncoder() @@ -364,53 +369,6 @@ extension Game: Codable { } } -// MARK: - Recent Game Card -struct RecentGameCard: View { - let game: Game - @Binding var startemu: Game? - @Environment(\.colorScheme) var colorScheme - - var body: some View { - Button(action: { - startemu = game - }) { - VStack(alignment: .leading, spacing: 8) { - if let icon = game.icon { - Image(uiImage: icon) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 140, height: 140) - .cornerRadius(12) - } else { - ZStack { - RoundedRectangle(cornerRadius: 12) - .fill(colorScheme == .dark ? - Color(.systemGray5) : Color(.systemGray6)) - .frame(width: 140, height: 140) - - Image(systemName: "gamecontroller.fill") - .font(.system(size: 40)) - .foregroundColor(.gray) - } - } - - VStack(alignment: .leading, spacing: 2) { - Text(game.titleName) - .font(.subheadline.bold()) - .lineLimit(1) - - Text(game.developer) - .font(.caption) - .foregroundColor(.secondary) - .lineLimit(1) - } - .padding(.horizontal, 4) - } - } - .buttonStyle(.plain) - } -} - // MARK: - Game List Item struct GameListRow: View { let game: Game @@ -469,56 +427,54 @@ struct GameListRow: View { .foregroundColor(.accentColor) .opacity(0.8) } - .contextMenu { - Section { - Button { - startemu = game - } label: { - Label("Play Now", systemImage: "play.fill") - } + } + .contextMenu { + Section { + Button { + startemu = game + } label: { + Label("Play Now", systemImage: "play.fill") + } + + Button { + gameInfo = game + isViewingGameInfo.toggle() - Button { - gameInfo = game - isViewingGameInfo.toggle() - - if game.titleName.lowercased() == "portal" { - gamepo = true - } else if game.titleName.lowercased() == "portal 2" { - gamepo = true - } - } label: { - Label("Game Info", systemImage: "info.circle") + if game.titleName.lowercased() == "portal" { + gamepo = true + } else if game.titleName.lowercased() == "portal 2" { + gamepo = true } + } label: { + Label("Game Info", systemImage: "info.circle") + } + } + + Section { + Button { + gameInfo = game + isSelectingGameUpdate.toggle() + } label: { + Label("Game Update Manager", systemImage: "chevron.up.circle") } - Section { - Button { - gameInfo = game - isSelectingGameUpdate.toggle() - } label: { - Label("Game Update Manager", systemImage: "chevron.up.circle") - } - - Button { - gameInfo = game - isSelectingGameDLC.toggle() - } label: { - Label("Game DLC Manager", systemImage: "plus.viewfinder") - } + Button { + gameInfo = game + isSelectingGameDLC.toggle() + } label: { + Label("Game DLC Manager", systemImage: "plus.viewfinder") } - - - Section { - Button(role: .destructive) { - gametoDelete = game - showGameDeleteConfirmation.toggle() - } label: { - Label("Delete", systemImage: "trash") - } + } + + 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 { diff --git a/src/MeloNX/MeloNX/App/Views/Updates/GameDLCManagerSheet.swift b/src/MeloNX/MeloNX/App/Views/Updates/GameDLCManagerSheet.swift index 5e751dd48..589c16545 100644 --- a/src/MeloNX/MeloNX/App/Views/Updates/GameDLCManagerSheet.swift +++ b/src/MeloNX/MeloNX/App/Views/Updates/GameDLCManagerSheet.swift @@ -45,7 +45,7 @@ struct DLCManagerSheet: View { Self.saveDlcs(game, dlc: dlcs) }) { HStack { - Text(dlc.containerPath) + Text((dlc.containerPath as NSString).lastPathComponent) .foregroundStyle(Color(uiColor: .label)) Spacer() if dlc.downloadableContentNcaList.first?.enabled == true {