Skip to content

Commit fc089d7

Browse files
committed
Adds a table view along with list and grid views, splits view implementation
1 parent ab2592e commit fc089d7

18 files changed

Lines changed: 1613 additions & 503 deletions

Application/AppDelegate.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
4848
}
4949

5050
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
51-
print("applicationShouldHandleReopen - hasVisibleWindows: \(flag)")
52-
5351
// Always restore dock icon when reopening
5452
NSApp.setActivationPolicy(.regular)
5553

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Foundation
2+
import Combine
3+
4+
class ColumnVisibilityManager: ObservableObject {
5+
static let shared = ColumnVisibilityManager()
6+
7+
@Published var columnVisibility: TrackTableColumnVisibility {
8+
didSet {
9+
saveColumnVisibility()
10+
}
11+
}
12+
13+
private let userDefaultsKey = "trackTableColumnVisibility"
14+
15+
private init() {
16+
// Load from UserDefaults on init
17+
if let data = UserDefaults.standard.data(forKey: userDefaultsKey),
18+
let decoded = try? JSONDecoder().decode(TrackTableColumnVisibility.self, from: data) {
19+
self.columnVisibility = decoded
20+
} else {
21+
// Use default visibility settings
22+
self.columnVisibility = TrackTableColumnVisibility()
23+
}
24+
}
25+
26+
private func saveColumnVisibility() {
27+
// Save to UserDefaults whenever it changes
28+
if let encoded = try? JSONEncoder().encode(columnVisibility) {
29+
UserDefaults.standard.set(encoded, forKey: userDefaultsKey)
30+
}
31+
}
32+
33+
func toggleVisibility(_ column: TrackTableColumn) {
34+
columnVisibility.toggleVisibility(column)
35+
}
36+
37+
func isVisible(_ column: TrackTableColumn) -> Bool {
38+
columnVisibility.isVisible(column)
39+
}
40+
41+
func setVisibility(_ column: TrackTableColumn, isVisible: Bool) {
42+
columnVisibility.setVisibility(column, isVisible: isVisible)
43+
}
44+
}

Managers/LibraryManager.swift

Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ class LibraryManager: ObservableObject {
3434

3535
// MARK: - Initialization
3636
init() {
37-
print("LibraryManager: Initializing...")
38-
3937
do {
4038
// Initialize database manager
4139
databaseManager = try DatabaseManager()
@@ -60,14 +58,6 @@ class LibraryManager: ObservableObject {
6058
loadMusicLibrary()
6159
startFileWatcher()
6260

63-
// Register for memory pressure notifications
64-
NotificationCenter.default.addObserver(
65-
self,
66-
selector: #selector(handleMemoryPressure),
67-
name: NSNotification.Name.NSProcessInfoPowerStateDidChange,
68-
object: nil
69-
)
70-
7161
// Observe auto-scan interval changes
7262
NotificationCenter.default.addObserver(
7363
self,
@@ -113,7 +103,6 @@ class LibraryManager: ObservableObject {
113103
bookmarkDataIsStale: &isStale)
114104

115105
if isStale {
116-
print("LibraryManager: Bookmark is stale for \(urlString)")
117106
continue
118107
}
119108

@@ -124,8 +113,6 @@ class LibraryManager: ObservableObject {
124113
}
125114

126115
securityBookmarks[url] = bookmarkData
127-
print("LibraryManager: Restored security bookmark for \(url.lastPathComponent)")
128-
129116
} catch {
130117
print("LibraryManager: Failed to resolve bookmark for \(urlString): \(error)")
131118
}
@@ -416,36 +403,20 @@ class LibraryManager: ObservableObject {
416403
}
417404

418405
@objc private func autoScanIntervalDidChange(_ notification: Notification) {
406+
let newInterval = autoScanInterval
407+
408+
// Store the current interval to compare
409+
struct LastInterval {
410+
static var value: AutoScanInterval?
411+
}
412+
413+
// Only proceed if the interval actually changed
414+
guard LastInterval.value != newInterval else { return }
415+
LastInterval.value = newInterval
416+
419417
// Check if the auto-scan interval specifically changed
420418
DispatchQueue.main.async { [weak self] in
421419
self?.handleAutoScanIntervalChange()
422420
}
423421
}
424-
425-
// MARK: - Memory Management
426-
427-
@objc private func handleMemoryPressure() {
428-
print("LibraryManager: Handling memory pressure")
429-
430-
// With GRDB, we can simply reload data when needed
431-
// Clear the in-memory arrays to free memory
432-
if let coordinator = AppCoordinator.shared,
433-
let currentTrack = coordinator.audioPlayerManager.currentTrack {
434-
// Keep track of current track ID
435-
let currentTrackId = currentTrack.trackId
436-
437-
// Clear all tracks from memory
438-
tracks.removeAll()
439-
440-
// Reload just the essential data
441-
folders = databaseManager.getAllFolders()
442-
443-
print("LibraryManager: Cleared tracks from memory, keeping folders")
444-
} else {
445-
// No track playing, we can clear everything and reload when needed
446-
tracks.removeAll()
447-
folders.removeAll()
448-
print("LibraryManager: Cleared all data from memory")
449-
}
450-
}
451422
}

Models/Core/Track.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ class Track: Identifiable, ObservableObject, Equatable, FetchableRecord, Persist
213213
static func == (lhs: Track, rhs: Track) -> Bool {
214214
return lhs.id == rhs.id
215215
}
216+
217+
// MARK: - Sorting support
218+
219+
var albumArtistForSorting: String {
220+
return albumArtist ?? ""
221+
}
216222
}
217223

218224
// MARK: - Hashable Conformance

Models/Enums/LibraryViewType.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import Foundation
33
enum LibraryViewType: String, CaseIterable, Codable {
44
case list = "list"
55
case grid = "grid"
6+
case table = "table"
67

78
var displayName: String {
89
switch self {
910
case .list: return "List View"
1011
case .grid: return "Grid View"
12+
case .table: return "Table View"
1113
}
1214
}
1315
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import Foundation
2+
3+
enum SpecialTableColumn: String, Codable {
4+
case title = "title"
5+
case duration = "duration"
6+
7+
var displayName: String {
8+
switch self {
9+
case .title: return "Title"
10+
case .duration: return "Duration"
11+
}
12+
}
13+
}
14+
15+
enum TrackTableColumn: Codable, Hashable {
16+
case special(SpecialTableColumn)
17+
case libraryFilter(LibraryFilterType)
18+
19+
// All available columns in order
20+
static var allColumns: [TrackTableColumn] {
21+
var columns: [TrackTableColumn] = [.special(.title)]
22+
23+
// Add all LibraryFilterType columns
24+
for filterType in LibraryFilterType.allCases {
25+
columns.append(.libraryFilter(filterType))
26+
}
27+
28+
columns.append(.special(.duration))
29+
return columns
30+
}
31+
32+
var displayName: String {
33+
switch self {
34+
case .special(let specialColumn):
35+
return specialColumn.displayName
36+
case .libraryFilter(let filterType):
37+
return filterType.singularDisplayName
38+
}
39+
}
40+
41+
var identifier: String {
42+
switch self {
43+
case .special(let specialColumn):
44+
return specialColumn.rawValue
45+
case .libraryFilter(let filterType):
46+
return filterType.rawValue
47+
}
48+
}
49+
50+
var isRequired: Bool {
51+
switch self {
52+
case .special(.title):
53+
return true
54+
default:
55+
return false
56+
}
57+
}
58+
59+
var defaultVisibility: Bool {
60+
switch self {
61+
case .special(.title), .special(.duration):
62+
return true
63+
case .libraryFilter(let filterType):
64+
switch filterType {
65+
case .artists, .albums:
66+
return true
67+
default:
68+
return false
69+
}
70+
}
71+
}
72+
73+
// Codable implementation
74+
enum CodingKeys: String, CodingKey {
75+
case type
76+
case value
77+
}
78+
79+
func encode(to encoder: Encoder) throws {
80+
var container = encoder.container(keyedBy: CodingKeys.self)
81+
switch self {
82+
case .special(let specialColumn):
83+
try container.encode("special", forKey: .type)
84+
try container.encode(specialColumn.rawValue, forKey: .value)
85+
case .libraryFilter(let filterType):
86+
try container.encode("filter", forKey: .type)
87+
try container.encode(filterType.rawValue, forKey: .value)
88+
}
89+
}
90+
91+
init(from decoder: Decoder) throws {
92+
let container = try decoder.container(keyedBy: CodingKeys.self)
93+
let type = try container.decode(String.self, forKey: .type)
94+
95+
switch type {
96+
case "special":
97+
let value = try container.decode(String.self, forKey: .value)
98+
guard let specialColumn = SpecialTableColumn(rawValue: value) else {
99+
throw DecodingError.dataCorruptedError(forKey: .value, in: container, debugDescription: "Invalid special column")
100+
}
101+
self = .special(specialColumn)
102+
case "filter":
103+
let value = try container.decode(String.self, forKey: .value)
104+
guard let filterType = LibraryFilterType(rawValue: value) else {
105+
throw DecodingError.dataCorruptedError(forKey: .value, in: container, debugDescription: "Invalid filter type")
106+
}
107+
self = .libraryFilter(filterType)
108+
default:
109+
throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Invalid column type")
110+
}
111+
}
112+
}
113+
114+
struct TrackTableColumnVisibility: Codable {
115+
private var hiddenColumns: Set<String> // Store identifiers of hidden columns
116+
117+
init() {
118+
self.hiddenColumns = []
119+
120+
// Hide non-default columns
121+
for column in TrackTableColumn.allColumns {
122+
if !column.defaultVisibility && !column.isRequired {
123+
hiddenColumns.insert(column.identifier)
124+
}
125+
}
126+
}
127+
128+
func isVisible(_ column: TrackTableColumn) -> Bool {
129+
column.isRequired || !hiddenColumns.contains(column.identifier)
130+
}
131+
132+
mutating func setVisibility(_ column: TrackTableColumn, isVisible: Bool) {
133+
guard !column.isRequired else { return }
134+
135+
if isVisible {
136+
hiddenColumns.remove(column.identifier)
137+
} else {
138+
hiddenColumns.insert(column.identifier)
139+
}
140+
}
141+
142+
mutating func toggleVisibility(_ column: TrackTableColumn) {
143+
setVisibility(column, isVisible: !isVisible(column))
144+
}
145+
}

Views/Components/ContextualToolbar.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ struct ContextualToolbar: View {
3737

3838
private var viewToggleButtons: some View {
3939
TabbedButtons(
40-
items: [LibraryViewType.list, LibraryViewType.grid],
40+
items: [LibraryViewType.table, LibraryViewType.list, LibraryViewType.grid],
4141
selection: $viewType,
4242
style: .viewToggle
4343
)

Views/Components/TabbedButtons.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ extension LibraryViewType: TabbedItem {
205205
switch self {
206206
case .list: return "list.bullet"
207207
case .grid: return "square.grid.2x2"
208+
case .table: return "tablecells"
208209
}
209210
}
210211
}

0 commit comments

Comments
 (0)