Skip to content

Commit

Permalink
Add list layout option to game library
Browse files Browse the repository at this point in the history
  • Loading branch information
marcusziade committed Jun 24, 2024
1 parent 4ac2e4a commit 74b9a46
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 51 deletions.
12 changes: 8 additions & 4 deletions Mythic.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
objects = {

/* Begin PBXBuildFile section */
5A059CF82C2875D5004A19BF /* SparkleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A059CF72C2875D5004A19BF /* SparkleController.swift */; };
5A1584F12C27B0B300401048 /* GameListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1584F02C27B0B300401048 /* GameListRow.swift */; };
5A62AE982C27DB1200BA31D2 /* GameListEvoVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A62AE972C27DB1200BA31D2 /* GameListEvoVM.swift */; };
5A62AE9C2C27E1C400BA31D2 /* GameList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A62AE9B2C27E1C400BA31D2 /* GameList.swift */; };
5A9573AA2C29BBEC009C8F85 /* SparkleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9573A92C29BBEC009C8F85 /* SparkleController.swift */; };
6A0C66852ABC4092004B0755 /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = 6A0C66842ABC4092004B0755 /* CachedAsyncImage */; };
6A12FF8E2B73AC4E00AA948C /* Glur in Frameworks */ = {isa = PBXBuildFile; productRef = 6A12FF8D2B73AC4E00AA948C /* Glur */; };
6A2935322BFCFAFD0035CE4B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2934AE2BFCFAFD0035CE4B /* Preview Assets.xcassets */; };
Expand Down Expand Up @@ -96,9 +97,10 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
5A059CF72C2875D5004A19BF /* SparkleController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkleController.swift; sourceTree = "<group>"; };
5A1584F02C27B0B300401048 /* GameListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameListRow.swift; sourceTree = "<group>"; };
5A62AE972C27DB1200BA31D2 /* GameListEvoVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameListEvoVM.swift; sourceTree = "<group>"; };
5A62AE9B2C27E1C400BA31D2 /* GameList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameList.swift; sourceTree = "<group>"; };
5A9573A92C29BBEC009C8F85 /* SparkleController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkleController.swift; sourceTree = "<group>"; };
6A2934AE2BFCFAFD0035CE4B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
6A2934B02BFCFAFD0035CE4B /* Engine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Engine.swift; sourceTree = "<group>"; };
6A2934B12BFCFAFD0035CE4B /* EngineExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EngineExt.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -249,7 +251,7 @@
6A496A722C1AF75600FD637B /* Game.swift */,
6A2934C62BFCFAFD0035CE4B /* Global.swift */,
6A2934C72BFCFAFD0035CE4B /* NetworkMonitor.swift */,
5A059CF72C2875D5004A19BF /* SparkleController.swift */,
5A9573A92C29BBEC009C8F85 /* SparkleController.swift */,
6A2934C82BFCFAFD0035CE4B /* Rosetta.swift */,
6A2934C92BFCFAFD0035CE4B /* VariableManager.swift */,
);
Expand Down Expand Up @@ -328,6 +330,7 @@
6A2934E82BFCFAFD0035CE4B /* BottleListView.swift */,
6A2934E92BFCFAFD0035CE4B /* GameListEvoView.swift */,
5A62AE972C27DB1200BA31D2 /* GameListEvoVM.swift */,
5A1584F02C27B0B300401048 /* GameListRow.swift */,
6A2934EA2BFCFAFD0035CE4B /* NotImplementedView.swift */,
);
path = Unified;
Expand Down Expand Up @@ -544,10 +547,10 @@
6A2935422BFCFAFD0035CE4B /* WineInterfaceExt.swift in Sources */,
6A2935552BFCFAFD0035CE4B /* CompactGameCard.swift in Sources */,
6A29353F2BFCFAFD0035CE4B /* LocalGames.swift in Sources */,
5A9573AA2C29BBEC009C8F85 /* SparkleController.swift in Sources */,
6A29355B2BFCFAFD0035CE4B /* GameSettingsView.swift in Sources */,
6A2935442BFCFAFD0035CE4B /* Global.swift in Sources */,
6A2935632BFCFAFD0035CE4B /* WebView.swift in Sources */,
5A059CF82C2875D5004A19BF /* SparkleController.swift in Sources */,
6A2935582BFCFAFD0035CE4B /* SubscriptedTextView.swift in Sources */,
6A2935542BFCFAFD0035CE4B /* Support.swift in Sources */,
6A29354E2BFCFAFD0035CE4B /* Bottles.swift in Sources */,
Expand All @@ -559,6 +562,7 @@
6A29354B2BFCFAFD0035CE4B /* LocalImport.swift in Sources */,
6A29355C2BFCFAFD0035CE4B /* InstallGameView.swift in Sources */,
6A2935432BFCFAFD0035CE4B /* FileLocations.swift in Sources */,
5A1584F12C27B0B300401048 /* GameListRow.swift in Sources */,
6A2935402BFCFAFD0035CE4B /* LocalGamesExt.swift in Sources */,
6A2935502BFCFAFD0035CE4B /* Home.swift in Sources */,
6A29354D2BFCFAFD0035CE4B /* Accounts.swift in Sources */,
Expand Down
19 changes: 19 additions & 0 deletions Mythic/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -4046,6 +4046,9 @@
}
}
}
},
"Add to favorites" : {

},
"Are you sure you want to cancel bottle creation?" : {
"localizations" : {
Expand Down Expand Up @@ -17227,6 +17230,19 @@
}
}
},
"Game: %@" : {

},
"Game: %@, Type: %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Game: %1$@, Type: %2$@"
}
}
}
},
"Get support/Support Mythic" : {
"localizations" : {
"af" : {
Expand Down Expand Up @@ -28714,6 +28730,9 @@
},
"Remove from download queue" : {

},
"Remove from favorites" : {

},
"Remove Mythic Engine" : {
"localizations" : {
Expand Down
2 changes: 1 addition & 1 deletion Mythic/Utilities/Wine/WineInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ final class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416
}

// TODO: Implement tasklist
/// Not implemented yet -- unnecessary at this time
// Not implemented yet -- unnecessary at this time
/*
static func tasklist(bottleURL url: URL) throws -> [String: Int] {
let list: [String: Int] = .init()
Expand Down
149 changes: 103 additions & 46 deletions Mythic/Views/Unified/GameListEvoView.swift
Original file line number Diff line number Diff line change
@@ -1,77 +1,134 @@
import Foundation
import SwiftUI

struct GameListEvo: View {
@State private var viewModel = GameListVM()
@State private var searchString: String = .init()
@State private var refresh: Bool = false
@State private var isGameImportViewPresented: Bool = false
@State private var filterOptions: FilterOptions = .init()
@AppStorage("game_libary/is_list_layout_enabled") private var isListLayoutEnabled: Bool = false

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] {
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
}
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 {
VStack {
filterBar

if !unifiedGames.isEmpty {
gameList
if isListLayoutEnabled {
List {
ForEach(games) { game in
GameListRow(game: .constant(game))
}
}
.searchable(text: $searchString, placement: .toolbar)
} else {
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 {
emptyStateView
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: $viewModel.searchString, placement: .toolbar)
.onChange(of: viewModel.searchString) { _, _ in
viewModel.debouncedUpdateGames()
}
}
}

private extension GameListEvo {

var filterBar: some View {
private var filterBar: some View {
HStack {
Toggle("Installed", isOn: $viewModel.filterOptions.showInstalled)
Toggle("Installed", isOn: $filterOptions.showInstalled)

Picker("Platform", selection: $viewModel.filterOptions.platform) {
Picker("Platform", selection: $filterOptions.platform) {
ForEach(Platform.allCases, id: \.self) { platform in
Text(platform.rawValue).tag(platform)
}
}

Picker("Source", selection: $viewModel.filterOptions.source) {
Picker("Source", selection: $filterOptions.source) {
ForEach(GameSource.allCases, id: \.self) { source in
Text(source.rawValue).tag(source)
}
}
}
.padding()
}

var gameList: some View {
ScrollView(.horizontal) {
LazyHGrid(rows: [.init(.adaptive(minimum: 335))]) {
ForEach(viewModel.games) { game in
GameCard(game: .constant(game))
.padding([.leading, .vertical])
}

Spacer()

Button {
isListLayoutEnabled.toggle()
} label: {
Image(systemName: isListLayoutEnabled ? "square.grid.2x2" : "list.bullet")
}
}
}

var emptyStateView: some View {
VStack {
Text("No games can be shown.")
.font(.bold(.title)())
importGameButton
}
}

var importGameButton: some View {
Button {
isGameImportViewPresented = true
} label: {
Label("Import game", systemImage: "plus.app")
.padding(5)
}
.buttonStyle(.borderedProminent)
.sheet(isPresented: $isGameImportViewPresented) {
LibraryView.GameImportView(isPresented: $isGameImportViewPresented)
}
.padding()
}
}

Expand Down
Loading

0 comments on commit 74b9a46

Please sign in to comment.