Skip to content

Commit 55000ab

Browse files
committed
fixes
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
1 parent d565261 commit 55000ab

24 files changed

+1683
-173
lines changed

App/KasetApp.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,16 @@ struct KasetApp: App {
158158
playerService.cycleRepeatMode()
159159
}
160160
.keyboardShortcut("r", modifiers: .command)
161+
162+
Divider()
163+
164+
// Lyrics - ⌘L
165+
Button(playerService.showLyrics ? "Hide Lyrics" : "Show Lyrics") {
166+
withAnimation(.easeInOut(duration: 0.2)) {
167+
playerService.showLyrics.toggle()
168+
}
169+
}
170+
.keyboardShortcut("l", modifiers: .command)
161171
}
162172

163173
// Navigation commands - replace default sidebar toggle

Core/Services/API/YTMusicClient.swift

Lines changed: 71 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,11 @@ final class YTMusicClient: YTMusicClientProtocol {
3232

3333
// MARK: - Public API Methods
3434

35-
/// Maximum number of continuations to fetch initially for faster perceived load.
36-
private static let maxInitialContinuations = 3
37-
3835
/// Maximum total continuations to prevent infinite loops.
39-
private static let maxTotalContinuations = 10
36+
private static let maxTotalContinuations = 5
4037

41-
/// Fetches the home page content with all sections (including continuations).
38+
/// Fetches the home page content (initial sections only for fast display).
39+
/// Call `getHomeContinuation` to load additional sections progressively.
4240
func getHome() async throws -> HomeResponse {
4341
logger.info("Fetching home page")
4442

@@ -47,90 +45,50 @@ final class YTMusicClient: YTMusicClientProtocol {
4745
]
4846

4947
let data = try await request("browse", body: body, ttl: APICache.TTL.home)
50-
var response = HomeResponseParser.parse(data)
51-
52-
// Fetch limited continuations for faster initial load
53-
var continuationToken = HomeResponseParser.extractContinuationToken(from: data)
54-
var continuationCount = 0
55-
56-
while let token = continuationToken, continuationCount < Self.maxInitialContinuations {
57-
continuationCount += 1
58-
logger.info("Fetching home continuation \(continuationCount)")
59-
60-
do {
61-
let continuationData = try await requestContinuation(token)
62-
let additionalSections = HomeResponseParser.parseContinuation(continuationData)
63-
response = HomeResponse(sections: response.sections + additionalSections)
64-
continuationToken = HomeResponseParser.extractContinuationTokenFromContinuation(continuationData)
65-
} catch {
66-
logger.warning("Failed to fetch continuation: \(error.localizedDescription)")
67-
break
68-
}
69-
}
48+
let response = HomeResponseParser.parse(data)
49+
50+
// Store continuation token for progressive loading
51+
let token = HomeResponseParser.extractContinuationToken(from: data)
52+
_homeContinuationToken = token
7053

71-
logger.info("Total home sections after initial continuations: \(response.sections.count)")
54+
logger.info("Home page loaded: \(response.sections.count) initial sections, hasMore: \(token != nil)")
7255
return response
7356
}
7457

75-
/// Fetches additional home sections via continuation.
76-
/// Call this to progressively load more content after initial load.
77-
/// - Parameter currentSections: The current sections to append to
78-
/// - Returns: Updated HomeResponse with additional sections, or nil if no more available
79-
func getHomeMore(after currentSections: [HomeSection]) async throws -> HomeResponse? {
80-
logger.info("Fetching more home sections")
81-
82-
// We need to refetch and skip to the continuation point
83-
// This is a simplified approach - in production, you'd cache the continuation token
84-
let body: [String: Any] = [
85-
"browseId": "FEmusic_home",
86-
]
58+
/// Internal storage for continuation token (reset on each getHome call).
59+
private var _homeContinuationToken: String?
8760

88-
let data = try await request("browse", body: body, ttl: APICache.TTL.home)
89-
var token = HomeResponseParser.extractContinuationToken(from: data)
90-
91-
// Skip the continuations we already have (approximation based on section count)
92-
var skipped = 0
93-
let skipCount = max(0, Self.maxInitialContinuations)
94-
95-
while let currentToken = token, skipped < skipCount {
96-
let continuationData = try await requestContinuation(currentToken)
97-
token = HomeResponseParser.extractContinuationTokenFromContinuation(continuationData)
98-
skipped += 1
99-
}
100-
101-
// Now fetch additional sections
102-
guard let nextToken = token else {
103-
logger.info("No more home continuations available")
61+
/// Fetches the next batch of home sections via continuation.
62+
/// Returns nil if no more sections are available.
63+
func getHomeContinuation() async throws -> [HomeSection]? {
64+
guard let token = _homeContinuationToken else {
65+
logger.debug("No home continuation token available")
10466
return nil
10567
}
10668

107-
var additionalSections: [HomeSection] = []
108-
var continuationToken: String? = nextToken
109-
var continuationCount = 0
110-
let maxAdditional = Self.maxTotalContinuations - Self.maxInitialContinuations
69+
logger.info("Fetching home continuation")
11170

112-
while let currentToken = continuationToken, continuationCount < maxAdditional {
113-
continuationCount += 1
114-
logger.info("Fetching additional home continuation \(continuationCount)")
71+
do {
72+
let continuationData = try await requestContinuation(token)
73+
let additionalSections = HomeResponseParser.parseContinuation(continuationData)
74+
_homeContinuationToken = HomeResponseParser.extractContinuationTokenFromContinuation(continuationData)
75+
let hasMore = _homeContinuationToken != nil
11576

116-
do {
117-
let continuationData = try await requestContinuation(currentToken)
118-
let sections = HomeResponseParser.parseContinuation(continuationData)
119-
additionalSections.append(contentsOf: sections)
120-
continuationToken = HomeResponseParser.extractContinuationTokenFromContinuation(continuationData)
121-
} catch {
122-
logger.warning("Failed to fetch additional continuation: \(error.localizedDescription)")
123-
break
124-
}
77+
logger.info("Home continuation loaded: \(additionalSections.count) sections, hasMore: \(hasMore)")
78+
return additionalSections
79+
} catch {
80+
logger.warning("Failed to fetch home continuation: \(error.localizedDescription)")
81+
_homeContinuationToken = nil
82+
throw error
12583
}
84+
}
12685

127-
guard !additionalSections.isEmpty else { return nil }
128-
129-
logger.info("Fetched \(additionalSections.count) additional home sections")
130-
return HomeResponse(sections: currentSections + additionalSections)
86+
/// Whether more home sections are available to load.
87+
var hasMoreHomeSections: Bool {
88+
_homeContinuationToken != nil
13189
}
13290

133-
/// Fetches the explore page content with all sections.
91+
/// Fetches the explore page content (initial sections only for fast display).
13492
func getExplore() async throws -> HomeResponse {
13593
logger.info("Fetching explore page")
13694

@@ -139,31 +97,48 @@ final class YTMusicClient: YTMusicClientProtocol {
13997
]
14098

14199
let data = try await request("browse", body: body, ttl: APICache.TTL.home)
142-
var response = HomeResponseParser.parse(data)
143-
144-
// Fetch limited continuations for faster initial load
145-
var continuationToken = HomeResponseParser.extractContinuationToken(from: data)
146-
var continuationCount = 0
147-
148-
while let token = continuationToken, continuationCount < Self.maxInitialContinuations {
149-
continuationCount += 1
150-
logger.info("Fetching explore continuation \(continuationCount)")
151-
152-
do {
153-
let continuationData = try await requestContinuation(token)
154-
let additionalSections = HomeResponseParser.parseContinuation(continuationData)
155-
response = HomeResponse(sections: response.sections + additionalSections)
156-
continuationToken = HomeResponseParser.extractContinuationTokenFromContinuation(continuationData)
157-
} catch {
158-
logger.warning("Failed to fetch continuation: \(error.localizedDescription)")
159-
break
160-
}
161-
}
100+
let response = HomeResponseParser.parse(data)
101+
102+
// Store continuation token for progressive loading
103+
let token = HomeResponseParser.extractContinuationToken(from: data)
104+
_exploreContinuationToken = token
162105

163-
logger.info("Total explore sections after initial continuations: \(response.sections.count)")
106+
logger.info("Explore page loaded: \(response.sections.count) initial sections, hasMore: \(token != nil)")
164107
return response
165108
}
166109

110+
/// Internal storage for explore continuation token.
111+
private var _exploreContinuationToken: String?
112+
113+
/// Fetches the next batch of explore sections via continuation.
114+
func getExploreContinuation() async throws -> [HomeSection]? {
115+
guard let token = _exploreContinuationToken else {
116+
logger.debug("No explore continuation token available")
117+
return nil
118+
}
119+
120+
logger.info("Fetching explore continuation")
121+
122+
do {
123+
let continuationData = try await requestContinuation(token)
124+
let additionalSections = HomeResponseParser.parseContinuation(continuationData)
125+
_exploreContinuationToken = HomeResponseParser.extractContinuationTokenFromContinuation(continuationData)
126+
let hasMore = _exploreContinuationToken != nil
127+
128+
logger.info("Explore continuation loaded: \(additionalSections.count) sections, hasMore: \(hasMore)")
129+
return additionalSections
130+
} catch {
131+
logger.warning("Failed to fetch explore continuation: \(error.localizedDescription)")
132+
_exploreContinuationToken = nil
133+
throw error
134+
}
135+
}
136+
137+
/// Whether more explore sections are available to load.
138+
var hasMoreExploreSections: Bool {
139+
_exploreContinuationToken != nil
140+
}
141+
167142
/// Makes a continuation request.
168143
private func requestContinuation(_ token: String, ttl: TimeInterval? = APICache.TTL.home) async throws -> [String: Any] {
169144
let body: [String: Any] = [

Core/Services/Protocols.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,24 @@ import Foundation
66
/// Enables dependency injection and mocking for tests.
77
@MainActor
88
protocol YTMusicClientProtocol: Sendable {
9-
/// Fetches the home page content with all sections.
9+
/// Fetches the home page content (initial sections only for fast display).
1010
func getHome() async throws -> HomeResponse
1111

12-
/// Fetches the explore page content with all sections.
12+
/// Fetches the next batch of home sections via continuation.
13+
func getHomeContinuation() async throws -> [HomeSection]?
14+
15+
/// Whether more home sections are available to load.
16+
var hasMoreHomeSections: Bool { get }
17+
18+
/// Fetches the explore page content (initial sections only for fast display).
1319
func getExplore() async throws -> HomeResponse
1420

21+
/// Fetches the next batch of explore sections via continuation.
22+
func getExploreContinuation() async throws -> [HomeSection]?
23+
24+
/// Whether more explore sections are available to load.
25+
var hasMoreExploreSections: Bool { get }
26+
1527
/// Searches for content.
1628
func search(query: String) async throws -> SearchResponse
1729

0 commit comments

Comments
 (0)