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

Implement search, sort picker, favorite games, and sort by recency #52

Merged
merged 12 commits into from
Aug 27, 2023
7 changes: 4 additions & 3 deletions Phoenix/Phoenix/AddGameView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ struct AddGameView: View {
@State private var nameInput: String = ""
@State private var iconInput: String = ""
@State private var iconOutput: String = ""
@State private var platInput: Platform = .NONE
@State private var statusInput: Status = .NONE
@State private var platInput: Platform = .none
@State private var statusInput: Status = .none
@State private var cmdInput: String = ""
@State private var descInput: String = ""
@State private var headInput: String = ""
Expand Down Expand Up @@ -292,7 +292,8 @@ struct AddGameView: View {
name: nameInput,
platform: platInput,
status: statusInput,
is_deleted: false
is_deleted: false,
is_favorite: false
)

games.append(newGame)
Expand Down
6 changes: 6 additions & 0 deletions Phoenix/Phoenix/AppearanceSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ struct AppearanceSettingsView: View {
@AppStorage("listIconSize")
private var listIconSize: Double = 24

@AppStorage("picker")
private var picker: Bool = true

var body: some View {
Form {
VStack(alignment: .leading, spacing: 20) {
Expand All @@ -41,6 +44,9 @@ struct AppearanceSettingsView: View {
}
.frame(maxWidth: 225)
.opacity(listIconsHidden ? 0 : 1)
Toggle(isOn: $picker) {
Text("Show sidebar category picker")
}
}
}
}
Expand Down
45 changes: 40 additions & 5 deletions Phoenix/Phoenix/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ var games = loadGames().games.sorted()

struct ContentView: View {
@Environment(\.openWindow) var openWindow
@Binding var sortByPlatform: Bool
@State var selectedGame: String?
@Binding var sortBy: PhoenixApp.SortBy
@State var searchText: String = ""
@Binding var selectedGame: String?
@State var refresh: Bool = false
@State private var isAddingGame: Bool = false
@State private var timer: Timer?
@Binding var isAddingGame: Bool
@Binding var isEditingGame: Bool
@Binding var isPlayingGame: Bool
@State var picker: Bool = true

// The stuff that is actually on screen
var body: some View {
NavigationSplitView {
// The sidebar
GameListView(sortByPlatform: $sortByPlatform, selectedGame: $selectedGame, refresh: $refresh)
GameListView(sortBy: $sortBy, selectedGame: $selectedGame, refresh: $refresh, searchText: $searchText)
.toolbar {
ToolbarItem(placement: .primaryAction) {
// Add game button
Expand All @@ -46,14 +51,44 @@ struct ContentView: View {
}
)
}
if picker {
ToolbarItem(placement: .primaryAction) {
Picker("Sort by", selection: $sortBy) {
ForEach(PhoenixApp.SortBy.allCases) { sortBy in
HStack(alignment: .center, spacing: 5) {
Image(systemName: sortBy.symbol)
Text(sortBy.displayName)
}
}
}
.pickerStyle(.automatic)
}
}
}
} detail: {
// The detailed view of the selected game
GameDetailView(selectedGame: $selectedGame, refresh: $refresh)
GameDetailView(selectedGame: $selectedGame, refresh: $refresh, editingGame: $isEditingGame, playingGame: $isPlayingGame)

// Refresh detail view
Text(String(refresh))
.hidden()
}
.onAppear {
if UserDefaults.standard.bool(forKey: "picker") {
picker = true
} else {
picker = false
}
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
if UserDefaults.standard.bool(forKey: "picker") {
picker = true
} else {
picker = false
}
refresh.toggle()
// This code will be executed every 1 second
}
}
.searchable(text: $searchText, placement: .sidebar)
}
}
11 changes: 6 additions & 5 deletions Phoenix/Phoenix/EditGameView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ struct EditGameView: View {
@State private var nameInput: String = ""
@State private var iconInput: String = ""
@State private var iconOutput: String = ""
@State private var platInput: Platform = .NONE
@State private var statusInput: Status = .NONE
@State private var platInput: Platform = .none
@State private var statusInput: Status = .none
@State private var cmdInput: String = ""
@State private var descInput: String = ""
@State private var headInput: String = ""
Expand Down Expand Up @@ -320,10 +320,10 @@ struct EditGameView: View {
if iconOutput == "" {
iconOutput = currentGame.icon
}
if platInput == .NONE {
if platInput == .none {
platInput = currentGame.platform
}
if statusInput == .NONE {
if statusInput == .none {
statusInput = currentGame.status
}
if cmdInput == "" {
Expand Down Expand Up @@ -375,7 +375,8 @@ struct EditGameView: View {
name: nameInput,
platform: platInput,
status: statusInput,
is_deleted: false
is_deleted: currentGame.is_deleted,
is_favorite: currentGame.is_favorite
)

let idx = games.firstIndex(where: { $0.name == currentGame.name })
Expand Down
123 changes: 91 additions & 32 deletions Phoenix/Phoenix/Game.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,57 @@
import Foundation

enum Platform: String, Codable, CaseIterable, Identifiable {
case MAC, STEAM, GOG, EPIC, PC, PS, NIN, XBOX, NONE
case mac, steam, gog, epic, pc, ps, nin, xbox, none

var id: Platform { self }

var displayName: String {
switch self {
case .MAC: return "Mac"
case .STEAM: return "Steam"
case .GOG: return "GOG"
case .EPIC: return "Epic"
case .PC: return "PC"
case .PS: return "Playstation"
case .NIN: return "Nintendo"
case .XBOX: return "Xbox"
case .NONE: return "Other"
case .mac: return "Mac"
case .steam: return "Steam"
case .gog: return "GOG"
case .epic: return "Epic"
case .pc: return "PC"
case .ps: return "Playstation"
case .nin: return "Nintendo"
case .xbox: return "Xbox"
case .none: return "Other"
}
}
}

enum Status: String, Codable, CaseIterable, Identifiable {
case PLAYING, SHELVED, BACKLOG, BEATEN, COMPLETED, ABANDONED, NONE
case playing, shelved, backlog, beaten, completed, abandoned, none

var id: Status { self }

var displayName: String {
switch self {
case .BACKLOG: return "Backlog"
case .PLAYING: return "Playing"
case .BEATEN: return "Beaten"
case .COMPLETED: return "Completed"
case .SHELVED: return "Shelved"
case .ABANDONED: return "Abandoned"
case .NONE: return "Other"
case .playing: return "Playing"
case .shelved: return "Shelved"
case .backlog: return "Backlog"
case .beaten: return "Beaten"
case .completed: return "Completed"
case .abandoned: return "Abandoned"
case .none: return "Other"
}
}
}

enum Recency: String, Codable, CaseIterable, Identifiable {
case day, week, month, three_months, six_months, year, never

var id: Recency { self }

var displayName: String {
switch self {
case .day: return "Today"
case .week: return "This Week"
case .month: return "This Month"
case .three_months: return "Last 3 Months"
case .six_months: return "Last 6 Months"
case .year: return "This Year"
case .never: return "Never"
}
}
}
Expand All @@ -53,7 +71,9 @@ struct Game: Codable, Comparable, Hashable {
var name: String
var platform: Platform
var status: Status
var is_deleted: Bool // New property to indicate if the game has been deleted
var recency: Recency
var is_deleted: Bool
var is_favorite: Bool

init(
appID: String = "",
Expand All @@ -70,9 +90,11 @@ struct Game: Codable, Comparable, Hashable {
],
icon: String = "PlaceholderImage",
name: String,
platform: Platform = Platform.NONE,
status: Status = Status.NONE,
is_deleted: Bool
platform: Platform = Platform.none,
status: Status = Status.none,
recency: Recency = Recency.never,
is_deleted: Bool,
is_favorite: Bool
) {
self.appID = appID
self.launcher = launcher
Expand All @@ -81,11 +103,13 @@ struct Game: Codable, Comparable, Hashable {
self.name = name
self.platform = platform
self.status = status
self.recency = recency
self.is_deleted = is_deleted
self.is_favorite = is_favorite
}

enum CodingKeys: String, CodingKey {
case appID, launcher, metadata, icon, name, platform, status, is_deleted
case appID, launcher, metadata, icon, name, platform, status, recency, is_deleted, is_favorite
}

init(from decoder: Decoder) throws {
Expand All @@ -95,21 +119,49 @@ struct Game: Codable, Comparable, Hashable {
icon = try container.decode(String.self, forKey: .icon)
name = try container.decode(String.self, forKey: .name)

// If game platform was .EMUL change to .NONE
let platformRawValue = try container.decode(String.self, forKey: .platform)
if platformRawValue == "EMUL" {
self.platform = .NONE
} else if let platform = Platform(rawValue: platformRawValue) {
// If game platform was .EMUL change to .none
var platformRawValue = try container.decode(String.self, forKey: .platform)
platformRawValue = platformRawValue.lowercased()

if let platform = Platform(rawValue: platformRawValue) {
self.platform = platform
} else {
self.platform = .NONE
self.platform = .none
}

// Handle status conversion with default to .NONE
if let status = try? container.decode(Status.self, forKey: .status) {
var statusRawValue = try container.decode(String.self, forKey: .status)
statusRawValue = statusRawValue.lowercased()

if let status = Status(rawValue: statusRawValue) {
self.status = status
} else {
self.status = .NONE
self.status = .none
}

// Decode recency, or derive it from last_played
let dateString = metadata["last_played"] ?? ""
if dateString == "" || dateString == "Never" {
self.recency = .never
} else {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM dd, yyyy"
let lastPlayedDate = dateFormatter.date(from: dateString)
let timeInterval = lastPlayedDate?.timeIntervalSinceNow ?? 0
if abs(timeInterval) <= 24 * 60 * 60 {
self.recency = .day
} else if abs(timeInterval) <= 7 * 24 * 60 * 60 {
self.recency = .week
} else if abs(timeInterval) <= 30 * 24 * 60 * 60 {
self.recency = .month
} else if abs(timeInterval) <= 90 * 24 * 60 * 60 {
self.recency = .three_months
} else if abs(timeInterval) <= 180 * 24 * 60 * 60 {
self.recency = .six_months
} else if abs(timeInterval) <= 365 * 24 * 60 * 60 {
self.recency = .year
} else {
self.recency = .never
}
}

// Handle appID conversion with default to ""
Expand All @@ -125,6 +177,13 @@ struct Game: Codable, Comparable, Hashable {
} else {
self.is_deleted = false
}

// Handle appID conversion with default to ""
if let is_favorite = try? container.decode(Bool.self, forKey: .is_favorite) {
self.is_favorite = is_favorite
} else {
self.is_favorite = false
}
}

/**
Expand Down
Loading