forked from MeloNX/MeloNX
Recents fix + UI enhancements
This commit is contained in:
parent
8e60f6dc50
commit
abbb3bb0c3
@ -9,7 +9,7 @@ import SwiftUI
|
|||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
public struct Game: Identifiable, Equatable, Hashable {
|
public struct Game: Identifiable, Equatable, Hashable {
|
||||||
public var id = UUID()
|
public var id: String { titleId }
|
||||||
|
|
||||||
var containerFolder: URL
|
var containerFolder: URL
|
||||||
var fileType: UTType
|
var fileType: UTType
|
||||||
@ -21,7 +21,6 @@ public struct Game: Identifiable, Equatable, Hashable {
|
|||||||
var version: String
|
var version: String
|
||||||
var icon: UIImage?
|
var icon: UIImage?
|
||||||
|
|
||||||
|
|
||||||
static func convertGameInfoToGame(gameInfo: GameInfo, url: URL) -> Game {
|
static func convertGameInfoToGame(gameInfo: GameInfo, url: URL) -> Game {
|
||||||
var gameInfo = gameInfo
|
var gameInfo = gameInfo
|
||||||
var gameTemp = Game(containerFolder: url.deletingLastPathComponent(), fileType: .item, fileURL: url, titleName: "", titleId: "", developer: "", version: "")
|
var gameTemp = Game(containerFolder: url.deletingLastPathComponent(), fileType: .item, fileURL: url, titleName: "", titleId: "", developer: "", version: "")
|
||||||
|
@ -39,7 +39,9 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
var filteredGames: [Game] {
|
var filteredGames: [Game] {
|
||||||
if searchText.isEmpty {
|
if searchText.isEmpty {
|
||||||
return Ryujinx.shared.games
|
return Ryujinx.shared.games.filter { game in
|
||||||
|
!realRecentGames.contains(where: { $0.titleId == game.titleId })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Ryujinx.shared.games.filter {
|
return Ryujinx.shared.games.filter {
|
||||||
$0.titleName.localizedCaseInsensitiveContains(searchText) ||
|
$0.titleName.localizedCaseInsensitiveContains(searchText) ||
|
||||||
@ -47,6 +49,13 @@ struct GameLibraryView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var realRecentGames: [Game] {
|
||||||
|
let games = Ryujinx.shared.games
|
||||||
|
return recentGames.compactMap { recentGame in
|
||||||
|
games.first(where: { $0.titleId == recentGame.titleId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
iOSNav {
|
iOSNav {
|
||||||
List {
|
List {
|
||||||
@ -66,46 +75,32 @@ struct GameLibraryView: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.top, 40)
|
.padding(.top, 40)
|
||||||
} else {
|
} else {
|
||||||
if !isSearching && !recentGames.isEmpty {
|
if !isSearching && !realRecentGames.isEmpty {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
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")
|
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) {
|
Section {
|
||||||
Text("All Games")
|
|
||||||
.font(.title2.bold())
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
LazyVStack(spacing: 2) {
|
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
||||||
.onTapGesture {
|
|
||||||
addToRecentGames(game)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Others")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
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)
|
.searchable(text: $searchText)
|
||||||
|
.animation(.easeInOut, value: searchText)
|
||||||
.onChange(of: searchText) { _ in
|
.onChange(of: searchText) { _ in
|
||||||
isSearching = !searchText.isEmpty
|
isSearching = !searchText.isEmpty
|
||||||
}
|
}
|
||||||
@ -291,7 +291,7 @@ struct GameLibraryView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func addToRecentGames(_ game: Game) {
|
private func addToRecentGames(_ game: Game) {
|
||||||
recentGames.removeAll { $0.id == game.id }
|
recentGames.removeAll { $0.titleId == game.titleId }
|
||||||
|
|
||||||
recentGames.insert(game, at: 0)
|
recentGames.insert(game, at: 0)
|
||||||
|
|
||||||
@ -302,6 +302,11 @@ struct GameLibraryView: View {
|
|||||||
saveRecentGames()
|
saveRecentGames()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func removeFromRecentGames(_ game: Game) {
|
||||||
|
recentGames.removeAll { $0.titleId == game.titleId }
|
||||||
|
saveRecentGames()
|
||||||
|
}
|
||||||
|
|
||||||
private func saveRecentGames() {
|
private func saveRecentGames() {
|
||||||
do {
|
do {
|
||||||
let encoder = JSONEncoder()
|
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
|
// MARK: - Game List Item
|
||||||
struct GameListRow: View {
|
struct GameListRow: View {
|
||||||
let game: Game
|
let game: Game
|
||||||
@ -467,6 +425,7 @@ struct GameListRow: View {
|
|||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
.opacity(0.8)
|
.opacity(0.8)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Section {
|
Section {
|
||||||
Button {
|
Button {
|
||||||
@ -508,8 +467,6 @@ struct GameListRow: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.confirmationDialog("Are you sure you want to delete this game?", isPresented: $showGameDeleteConfirmation) {
|
.confirmationDialog("Are you sure you want to delete this game?", isPresented: $showGameDeleteConfirmation) {
|
||||||
Button("Delete", role: .destructive) {
|
Button("Delete", role: .destructive) {
|
||||||
if let game = gametoDelete {
|
if let game = gametoDelete {
|
||||||
|
@ -45,7 +45,7 @@ struct DLCManagerSheet: View {
|
|||||||
Self.saveDlcs(game, dlc: dlcs)
|
Self.saveDlcs(game, dlc: dlcs)
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(dlc.containerPath)
|
Text((dlc.containerPath as NSString).lastPathComponent)
|
||||||
.foregroundStyle(Color(uiColor: .label))
|
.foregroundStyle(Color(uiColor: .label))
|
||||||
Spacer()
|
Spacer()
|
||||||
if dlc.downloadableContentNcaList.first?.enabled == true {
|
if dlc.downloadableContentNcaList.first?.enabled == true {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user