Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Game List with Advanced Filtering Options #113

Merged
merged 1 commit into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Mythic/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -19909,7 +19909,6 @@

},
"Installed" : {
"extractionState" : "stale",
"localizations" : {
"af" : {
"stringUnit" : {
Expand Down Expand Up @@ -27184,6 +27183,9 @@
}
}
}
},
"Platform" : {

},
"Play \"%@\"" : {
"localizations" : {
Expand Down Expand Up @@ -32341,6 +32343,9 @@
}
}
}
},
"Source" : {

},
"Status Unknown" : {
"extractionState" : "stale",
Expand Down
146 changes: 98 additions & 48 deletions Mythic/Views/Unified/GameListEvoView.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}

Expand Down