From df2b17ddd6b1fe8c23e281df07cb145f9285ee0d Mon Sep 17 00:00:00 2001 From: Daniil Vinogradov Date: Sun, 16 Feb 2025 13:16:39 +0100 Subject: [PATCH] UI enhancements --- .../App/Views/GamesList/GameListView.swift | 164 ++++++++---------- .../Updates/GameUpdateManagerSheet.swift | 68 ++++---- 2 files changed, 112 insertions(+), 120 deletions(-) diff --git a/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift b/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift index da759a4b4..96a1e51fb 100644 --- a/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift +++ b/src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift @@ -42,72 +42,54 @@ struct GameLibraryView: View { } return Ryujinx.shared.games.filter { $0.titleName.localizedCaseInsensitiveContains(searchText) || - $0.developer.localizedCaseInsensitiveContains(searchText) + $0.developer.localizedCaseInsensitiveContains(searchText) } } var body: some View { iOSNav { - ScrollView { - LazyVStack(alignment: .leading, spacing: 20) { - if !isSearching { - Text("Games") - .font(.system(size: 34, weight: .bold)) - .padding(.horizontal) - .padding(.top, 12) + List { + if Ryujinx.shared.games.isEmpty { + VStack(spacing: 16) { + Image(systemName: "gamecontroller.fill") + .font(.system(size: 64)) + .foregroundColor(.secondary.opacity(0.7)) + .padding(.top, 60) + Text("No Games Found") + .font(.title2.bold()) + .foregroundColor(.primary) + Text("Add ROM, Keys and Firmware to get started") + .font(.subheadline) + .foregroundColor(.secondary) } - - if Ryujinx.shared.games.isEmpty { - VStack(spacing: 16) { - Image(systemName: "gamecontroller.fill") - .font(.system(size: 64)) - .foregroundColor(.secondary.opacity(0.7)) - .padding(.top, 60) - Text("No Games Found") + .frame(maxWidth: .infinity) + .padding(.top, 40) + } else { + if !isSearching && !recentGames.isEmpty { + VStack(alignment: .leading, spacing: 12) { + Text("Recent") .font(.title2.bold()) - .foregroundColor(.primary) - Text("Add ROM, Keys and Firmware to get started") - .font(.subheadline) - .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity) - .padding(.top, 40) - } else { - if !isSearching && !recentGames.isEmpty { - VStack(alignment: .leading, spacing: 12) { - Text("Recent") - .font(.title2.bold()) - .padding(.horizontal) + .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, gameInfo: $gameInfo) + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(spacing: 16) { + ForEach(recentGames) { game in + RecentGameCard(game: game, startemu: $startemu) .onTapGesture { addToRecentGames(game) + startemu = game } } } + .padding(.horizontal) } - } else { + } + + 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, gameInfo: $gameInfo) @@ -117,42 +99,51 @@ struct GameLibraryView: View { } } } - } - } - .onAppear { - 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 - - Ryujinx.shared.installFirmware(firmwarePath: path) - - firmwareversion = (Ryujinx.shared.fetchFirmwareVersion() == "" ? "0" : Ryujinx.shared.fetchFirmwareVersion()) - if fun { - url.stopAccessingSecurityScopedResource() - } + } else { + ForEach(filteredGames) { game in + GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, gameInfo: $gameInfo) + .onTapGesture { + addToRecentGames(game) + } } - case .failure(let error): - print(error) } } } + .navigationTitle("Games") + .navigationBarTitleDisplayMode(.large) + .onAppear { + 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 + + Ryujinx.shared.installFirmware(firmwarePath: path) + + firmwareversion = (Ryujinx.shared.fetchFirmwareVersion() == "" ? "0" : Ryujinx.shared.fetchFirmwareVersion()) + if fun { + url.stopAccessingSecurityScopedResource() + } + } + case .failure(let error): + print(error) + } + } .toolbar { - ToolbarItem(placement: .topBarLeading) { + ToolbarItem(placement: .topBarTrailing) { Button { isSelectingGameFile.toggle() } label: { Image(systemName: "plus") } } - + ToolbarItem(placement: .topBarLeading) { Menu { Text("Firmware Version: \(firmwareversion)") @@ -215,7 +206,6 @@ struct GameLibraryView: View { } } } - .background(Color(.systemGroupedBackground)) .searchable(text: $searchText) .onChange(of: searchText) { _ in isSearching = !searchText.isEmpty @@ -296,7 +286,6 @@ struct GameLibraryView: View { } } - private func addToRecentGames(_ game: Game) { recentGames.removeAll { $0.id == game.id } @@ -329,8 +318,7 @@ struct GameLibraryView: View { } } - -// MARK: - Delete Game Function + // MARK: - Delete Game Function func deleteGame(game: Game) { let fileManager = FileManager.default do { @@ -343,7 +331,7 @@ struct GameLibraryView: View { } } -// MARK: -Game Model +// MARK: - Game Model extension Game: Codable { enum CodingKeys: String, CodingKey { case titleName, titleId, developer, version, fileURL @@ -372,7 +360,7 @@ extension Game: Codable { } } -// MARK: -Recent Game Card +// MARK: - Recent Game Card struct RecentGameCard: View { let game: Game @Binding var startemu: Game? @@ -393,7 +381,7 @@ struct RecentGameCard: View { ZStack { RoundedRectangle(cornerRadius: 12) .fill(colorScheme == .dark ? - Color(.systemGray5) : Color(.systemGray6)) + Color(.systemGray5) : Color(.systemGray6)) .frame(width: 140, height: 140) Image(systemName: "gamecontroller.fill") @@ -419,7 +407,7 @@ struct RecentGameCard: View { } } -// MARK: -Game List Item +// MARK: - Game List Item struct GameListRow: View { let game: Game @Binding var startemu: Game? @@ -447,7 +435,7 @@ struct GameListRow: View { ZStack { RoundedRectangle(cornerRadius: 8) .fill(colorScheme == .dark ? - Color(.systemGray5) : Color(.systemGray6)) + Color(.systemGray5) : Color(.systemGray6)) .frame(width: 45, height: 45) Image(systemName: "gamecontroller.fill") @@ -474,9 +462,6 @@ struct GameListRow: View { .foregroundColor(.accentColor) .opacity(0.8) } - .padding(.horizontal) - .padding(.vertical, 8) - .background(Color(.systemBackground)) .contextMenu { Section { Button { @@ -533,4 +518,3 @@ struct GameListRow: View { } } } - diff --git a/src/MeloNX/MeloNX/App/Views/Updates/GameUpdateManagerSheet.swift b/src/MeloNX/MeloNX/App/Views/Updates/GameUpdateManagerSheet.swift index da5209654..90211058b 100644 --- a/src/MeloNX/MeloNX/App/Views/Updates/GameUpdateManagerSheet.swift +++ b/src/MeloNX/MeloNX/App/Views/Updates/GameUpdateManagerSheet.swift @@ -15,39 +15,45 @@ struct UpdateManagerSheet: View { @Binding var game: Game? @State private var isSelectingGameUpdate = false @State private var jsonURL: URL? = nil - + var body: some View { NavigationView { - VStack { - List(paths, id: \..self) { item in - Button(action: { - selectItem(item.lastPathComponent) - }) { - HStack { - Text(item.lastPathComponent) - if selectedItem == "\(game!.titleId)/\(item.lastPathComponent)" { - Spacer() - Image(systemName: "checkmark") - } - } - } - .contextMenu { - Button { - removeUpdate(item) - } label: { - Text("Remove Update") + List(paths, id: \..self, selection: $selectedItem) { item in + Button(action: { + selectItem(item.lastPathComponent) + }) { + HStack { + Text(item.lastPathComponent) + .foregroundStyle(Color(uiColor: .label)) + Spacer() + if selectedItem == "\(game!.titleId)/\(item.lastPathComponent)" { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(Color.accentColor) + .font(.system(size: 24)) + } else { + Image(systemName: "circle") + .foregroundStyle(Color(uiColor: .secondaryLabel)) + .font(.system(size: 24)) } } } + .contextMenu { + Button { + removeUpdate(item) + } label: { + Text("Remove Update") + } + } } - .onAppear() { + .onAppear { print(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json")) loadJSON(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json")) } .navigationTitle("\(game!.titleName) Updates") + .navigationBarTitleDisplayMode(.inline) .toolbar { - Button("+") { + Button("Add", systemImage: "plus") { isSelectingGameUpdate = true } } @@ -80,7 +86,8 @@ struct UpdateManagerSheet: View { let destinationURL = romUpdatedDirectory.appendingPathComponent(url.lastPathComponent) try? fileManager.copyItem(at: url, to: destinationURL) - Ryujinx.shared.setTitleUpdate(titleId: gameInfo.titleId, updatePath: "\(gameInfo.titleId)/" + url.lastPathComponent) + items.append(gameInfo.titleId + "/" + url.lastPathComponent) + selectItem(url.lastPathComponent) Ryujinx.shared.games = Ryujinx.shared.loadGames() loadJSON(jsonURL!) } catch { @@ -108,6 +115,7 @@ struct UpdateManagerSheet: View { } saveJSON(selectedItem: selectedItem ?? "") + Ryujinx.shared.games = Ryujinx.shared.loadGames() } func saveJSON(selectedItem: String) { @@ -122,7 +130,6 @@ struct UpdateManagerSheet: View { } func loadJSON(_ json: URL) { - self.jsonURL = json print("Failed to read JSO") @@ -132,16 +139,17 @@ struct UpdateManagerSheet: View { do { let data = try Data(contentsOf: jsonURL) if let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], - let list = jsonDict["paths"] as? [String] { + let list = jsonDict["paths"] as? [String] + { var urls: [URL] = [] for path in list { urls.append(URL.documentsDirectory.appendingPathComponent("updates").appendingPathComponent(path)) } - self.items = list - self.paths = urls - self.selectedItem = jsonDict["selected"] as? String + items = list + paths = urls + selectedItem = jsonDict["selected"] as? String } } catch { print("Failed to read JSON: \(error)") @@ -155,8 +163,8 @@ struct UpdateManagerSheet: View { do { let newData = try JSONSerialization.data(withJSONObject: defaultData, options: .prettyPrinted) try newData.write(to: jsonURL) - self.items = [] - self.selectedItem = "" + items = [] + selectedItem = "" } catch { print("Failed to create default JSON: \(error)") } @@ -183,9 +191,9 @@ struct UpdateManagerSheet: View { let newData = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted) try newData.write(to: jsonURL) + Ryujinx.shared.games = Ryujinx.shared.loadGames() } catch { print("Failed to update JSON: \(error)") } } - }