From 3cea0f6cd08a3a6cd114447317c5b15648c958bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Ziad=C3=A9?= Date: Fri, 21 Jun 2024 22:23:48 -0700 Subject: [PATCH] Add filtering tools to `LibraryView` --- Mythic/Localizable.xcstrings | 7 +- Mythic/Views/Unified/GameListEvoView.swift | 146 ++++++++++++++------- 2 files changed, 104 insertions(+), 49 deletions(-) diff --git a/Mythic/Localizable.xcstrings b/Mythic/Localizable.xcstrings index 5b91cc6e..3fbaa766 100644 --- a/Mythic/Localizable.xcstrings +++ b/Mythic/Localizable.xcstrings @@ -19909,7 +19909,6 @@ }, "Installed" : { - "extractionState" : "stale", "localizations" : { "af" : { "stringUnit" : { @@ -27184,6 +27183,9 @@ } } } + }, + "Platform" : { + }, "Play \"%@\"" : { "localizations" : { @@ -32341,6 +32343,9 @@ } } } + }, + "Source" : { + }, "Status Unknown" : { "extractionState" : "stale", diff --git a/Mythic/Views/Unified/GameListEvoView.swift b/Mythic/Views/Unified/GameListEvoView.swift index 304096cd..e3f4c033 100644 --- a/Mythic/Views/Unified/GameListEvoView.swift +++ b/Mythic/Views/Unified/GameListEvoView.swift @@ -1,68 +1,118 @@ -// -// GameListEvo.swift -// Mythic -// -// Created by Esiayo Alegbe on 6/3/2024. -// - +import Foundation import SwiftUI struct GameListEvo: View { @State private var searchString: String = .init() @State private var refresh: Bool = false - @State private var isGameImportViewPresented: Bool = false - + @State private var filterOptions: FilterOptions = .init() + + struct FilterOptions { + var showInstalled: Bool = false + var platform: Platform = .all + var source: GameSource = .all + } + + enum Platform: String, CaseIterable { + case all = "All" + case mac = "macOS" + case windows = "Windows®" + } + + enum GameSource: String, CaseIterable { + case all = "All" + case epic = "Epic" + case steam = "Steam" + case local = "Local" + } + private var games: [Game] { - return unifiedGames - .filter { - searchString.isEmpty || - $0.title.localizedCaseInsensitiveContains(searchString) + let filteredGames = filterGames(unifiedGames) + return sortGames(filteredGames) + } + + private func filterGames(_ games: [Game]) -> [Game] { + games.filter { game in + let matchesSearch = searchString.isEmpty || game.title.localizedCaseInsensitiveContains(searchString) + let matchesInstalled = !filterOptions.showInstalled || isGameInstalled(game) + let matchesPlatform = filterOptions.platform == .all || game.platform?.rawValue == filterOptions.platform.rawValue + let matchesSource = filterOptions.source == .all || game.type.rawValue == filterOptions.source.rawValue + + return matchesSearch && matchesInstalled && matchesPlatform && matchesSource + } + } + + private func isGameInstalled(_ game: Game) -> Bool { + (try? Legendary.getInstalledGames().contains(game)) ?? false || (LocalGames.library?.contains(game) ?? false) + } + + private func sortGames(_ games: [Game]) -> [Game] { + games.sorted { game1, game2 in + if game1.isFavourited != game2.isFavourited { + return game1.isFavourited && !game2.isFavourited } - .sorted { - if $0.isFavourited != $1.isFavourited { - return $0.isFavourited && !$1.isFavourited - } - - if let games = try? Legendary.getInstalledGames(), games.contains($0) != games.contains($1) { - return games.contains($0) - } - - if let games = LocalGames.library, games.contains($0) != games.contains($1) { - return games.contains($0) - } - - return $0.title < $1.title + if let installedGames = try? Legendary.getInstalledGames(), + installedGames.contains(game1) != installedGames.contains(game2) + { + return installedGames.contains(game1) + } + if let localGames = LocalGames.library, + localGames.contains(game1) != localGames.contains(game2) + { + return localGames.contains(game1) } + return game1.title < game2.title + } } - + var body: some View { - if !unifiedGames.isEmpty { - ScrollView(.horizontal) { - LazyHGrid(rows: [.init(.adaptive(minimum: 335))]) { - ForEach(games) { game in - GameCard(game: .constant(game)) - .padding([.leading, .vertical]) + VStack { + filterBar + + if !unifiedGames.isEmpty { + ScrollView(.horizontal) { + LazyHGrid(rows: [.init(.adaptive(minimum: 335))]) { + ForEach(games) { game in + GameCard(game: .constant(game)) + .padding([.leading, .vertical]) + } } + .searchable(text: $searchString, placement: .toolbar) + } + } else { + Text("No games can be shown.") + .font(.bold(.title)()) + Button { + isGameImportViewPresented = true + } label: { + Label("Import game", systemImage: "plus.app") + .padding(5) + } + .buttonStyle(.borderedProminent) + .sheet(isPresented: $isGameImportViewPresented) { + LibraryView.GameImportView(isPresented: $isGameImportViewPresented) } - .searchable(text: $searchString, placement: .toolbar) } - } else { - Text("No games can be shown.") - .font(.bold(.title)()) - - Button { - isGameImportViewPresented = true - } label: { - Label("Import game", systemImage: "plus.app") - .padding(5) + } + } + + private var filterBar: some View { + HStack { + Toggle("Installed", isOn: $filterOptions.showInstalled) + + Picker("Platform", selection: $filterOptions.platform) { + ForEach(Platform.allCases, id: \.self) { platform in + Text(platform.rawValue).tag(platform) + } } - .buttonStyle(.borderedProminent) - .sheet(isPresented: $isGameImportViewPresented) { - LibraryView.GameImportView(isPresented: $isGameImportViewPresented) + + Picker("Source", selection: $filterOptions.source) { + ForEach(GameSource.allCases, id: \.self) { source in + Text(source.rawValue).tag(source) + } } - } + .padding() } }