|
1 | | -import React, { useState, useEffect, useContext } from 'react'; |
| 1 | +import React, { useState, useEffect, useContext, useCallback } from 'react'; |
2 | 2 | import { ShowLyrics } from './ShowLyrics'; |
3 | 3 | import { GetLyricsButton } from './GetLyricsButton'; |
4 | | -import Context from '../../Context/Context'; |
| 4 | +import { getLyricsFromLrcLib } from '../../Api/Songs'; |
| 5 | + |
| 6 | +// Constants for error messages |
| 7 | +const ERROR_MESSAGES = { |
| 8 | + NO_TRACK: 'No song playing or missing track information. Please play a song first.', |
| 9 | + OFFLINE: 'You are offline. Lyrics are not available in offline mode.', |
| 10 | + NOT_FOUND: 'No Lyrics Found\nSorry, we couldn\'t find lyrics for this song.', |
| 11 | + EMPTY_LYRICS: 'No Lyrics Found\nLyrics data is empty for this song.', |
| 12 | + FETCH_ERROR: 'Could not fetch lyrics. Please try again.' |
| 13 | +}; |
5 | 14 |
|
6 | | -export const LyricsHandler = ({ currentPlayingTrack, isOffline, Index, onLyricsVisibilityChange, currentArtworkSource }) => { |
7 | | - const [ShowDailog, setShowDailog] = useState(false); |
8 | | - const [Lyric, setLyric] = useState(null); |
9 | | - const [Loading, setLoading] = useState(false); |
10 | | - const { currentPlaylistData } = useContext(Context); |
| 15 | +/** |
| 16 | + * Handles fetching and displaying lyrics for the currently playing track |
| 17 | + */ |
| 18 | +export const LyricsHandler = ({ |
| 19 | + currentPlayingTrack, |
| 20 | + isOffline, |
| 21 | + onLyricsVisibilityChange, |
| 22 | + currentArtworkSource |
| 23 | +}) => { |
| 24 | + const [showDialog, setShowDialog] = useState(false); |
| 25 | + const [lyric, setLyric] = useState(null); |
| 26 | + const [isLoading, setIsLoading] = useState(false); |
| 27 | + |
| 28 | + // Notify parent component about dialog visibility changes |
| 29 | + useEffect(() => { |
| 30 | + onLyricsVisibilityChange?.(showDialog); |
| 31 | + }, [showDialog, onLyricsVisibilityChange]); |
11 | 32 |
|
| 33 | + // Clear lyrics when dialog is closed to prevent stale data |
12 | 34 | useEffect(() => { |
13 | | - if (onLyricsVisibilityChange) { |
14 | | - onLyricsVisibilityChange(ShowDailog); |
| 35 | + if (!showDialog) { |
| 36 | + setLyric(null); |
15 | 37 | } |
16 | | - }, [ShowDailog, onLyricsVisibilityChange]); |
17 | | - |
18 | | - const fetchLyrics = async () => { |
19 | | - if (!currentPlayingTrack?.id) { // Ensure there's a track to fetch lyrics for |
20 | | - setLyric({ plain: "No song selected." }); |
21 | | - setShowDailog(true); |
| 38 | + }, [currentPlayingTrack?.id, showDialog]); |
| 39 | + |
| 40 | + /** |
| 41 | + * Fetches lyrics for the current track |
| 42 | + */ |
| 43 | + const fetchLyrics = useCallback(async () => { |
| 44 | + if (!currentPlayingTrack?.title || !currentPlayingTrack?.artist) { |
| 45 | + setLyric({ plain: ERROR_MESSAGES.NO_TRACK }); |
| 46 | + setShowDialog(true); |
22 | 47 | return; |
23 | 48 | } |
24 | | - setShowDailog(true); |
25 | | - setLoading(true); |
| 49 | + |
| 50 | + setShowDialog(true); |
| 51 | + setIsLoading(true); |
26 | 52 | setLyric(null); |
27 | 53 |
|
28 | 54 | try { |
29 | | - if (!currentPlayingTrack?.title || !currentPlayingTrack?.artist) { |
30 | | - setLyric({ plain: "No song playing or missing track information. Please play a song first." }); |
31 | | - setLoading(false); |
32 | | - return; |
33 | | - } |
34 | | - |
35 | 55 | if (isOffline) { |
36 | | - setLyric({ plain: "You are offline. Lyrics are not available in offline mode." }); |
37 | | - setLoading(false); |
| 56 | + setLyric({ plain: ERROR_MESSAGES.OFFLINE }); |
38 | 57 | return; |
39 | 58 | } |
40 | 59 |
|
41 | | - let titleForAPI = null; |
42 | | - let artistForAPI = null; |
43 | | - let usedContextData = false; |
44 | | - |
45 | | - if ( |
46 | | - currentPlaylistData && |
47 | | - currentPlaylistData.length > 0 && |
48 | | - Index >= 0 && |
49 | | - Index < currentPlaylistData.length && |
50 | | - currentPlayingTrack && |
51 | | - currentPlaylistData[Index] && |
52 | | - currentPlaylistData[Index].id === currentPlayingTrack.id |
53 | | - ) { |
54 | | - const trackFromPlaylist = currentPlaylistData[Index]; |
55 | | - if (trackFromPlaylist.title && trackFromPlaylist.artist) { |
56 | | - titleForAPI = trackFromPlaylist.title; |
57 | | - artistForAPI = trackFromPlaylist.artist; |
58 | | - usedContextData = true; |
59 | | - // console.log(`LyricsHandler: Using title/artist from currentPlaylistData[${Index}]`); |
60 | | - } |
61 | | - } |
62 | | - |
63 | | - if (!titleForAPI && currentPlayingTrack?.title) { |
64 | | - titleForAPI = currentPlayingTrack.title; |
65 | | - } |
66 | | - if (!artistForAPI && currentPlayingTrack?.artist) { |
67 | | - artistForAPI = currentPlayingTrack.artist; |
68 | | - } |
| 60 | + const { artist, title } = currentPlayingTrack; |
| 61 | + const lyricsData = await getLyricsFromLrcLib(artist, title); |
69 | 62 |
|
70 | | - if (!usedContextData) { |
71 | | - if (titleForAPI && titleForAPI.endsWith("...")) { |
72 | | - titleForAPI = titleForAPI.substring(0, titleForAPI.length - 3).trim(); |
73 | | - } |
74 | | - if (artistForAPI && artistForAPI.endsWith("...")) { |
75 | | - artistForAPI = artistForAPI.substring(0, artistForAPI.length - 3).trim(); |
76 | | - } |
77 | | - if (artistForAPI && artistForAPI.includes(',')) { |
78 | | - artistForAPI = artistForAPI.split(',')[0].trim(); |
79 | | - } |
80 | | - } |
81 | | - |
82 | | - if (!titleForAPI || !artistForAPI) { |
83 | | - setLyric({ plain: "Missing track information for API call." }); |
84 | | - setLoading(false); |
| 63 | + if (!lyricsData?.success) { |
| 64 | + setLyric({ plain: lyricsData?.message || ERROR_MESSAGES.NOT_FOUND }); |
85 | 65 | return; |
86 | 66 | } |
87 | 67 |
|
88 | | - const trackName = encodeURIComponent(titleForAPI); |
89 | | - const artistName = encodeURIComponent(artistForAPI); |
| 68 | + const { syncedLyrics, plainLyrics } = lyricsData.data || {}; |
90 | 69 |
|
91 | | - // Construct the full API URL. Assuming lrclib.net structure. |
92 | | - // This was: `https://lrclib.net/api/search?artist_name=${artistName}&track_name=${trackName}` |
93 | | - // The getLyricsSongData function might encapsulate this, or expect parts. |
94 | | - const apiUrl = `https://lrclib.net/api/search?artist_name=${artistName}&track_name=${trackName}`; |
95 | | - // console.log(`LyricsHandler: Fetching lyrics from: ${apiUrl}`); |
96 | | - const apiResponse = await fetch(apiUrl); |
97 | | - |
98 | | - if (!apiResponse.ok) { |
99 | | - let errorMessage = `Failed to fetch lyrics. Status: ${apiResponse.status}`; |
100 | | - if (apiResponse.status === 404) { |
101 | | - errorMessage = "No Lyrics Found\nSorry, we couldn't find lyrics for this song."; |
102 | | - } else { |
103 | | - try { |
104 | | - const errorData = await apiResponse.json(); |
105 | | - errorMessage = errorData.message || `Service Error (${apiResponse.status})\nPlease try again later.`; |
106 | | - } catch (e) { |
107 | | - errorMessage = `Service Error (${apiResponse.status})\nPlease try again later.`; |
108 | | - } |
109 | | - } |
110 | | - setLyric({ plain: errorMessage }); |
111 | | - console.error('LyricsHandler: Lyrics fetch error:', errorMessage); |
112 | | - setLoading(false); |
113 | | - return; |
114 | | - } |
115 | | - |
116 | | - const results = await apiResponse.json(); |
117 | | - |
118 | | - if (results && results.length > 0) { |
119 | | - const firstMatch = results[0]; |
120 | | - // console.log('LyricsHandler: Lyrics API Response:', firstMatch); |
121 | | - if (firstMatch.syncedLyrics) { |
122 | | - setLyric({ synced: firstMatch.syncedLyrics, plain: firstMatch.plainLyrics }); |
123 | | - } else if (firstMatch.plainLyrics) { |
124 | | - setLyric({ plain: firstMatch.plainLyrics }); |
125 | | - } else { |
126 | | - setLyric({ plain: "No Lyrics Found\nLyrics data is empty for this song." }); |
127 | | - } |
| 70 | + if (syncedLyrics) { |
| 71 | + setLyric({ synced: syncedLyrics, plain: plainLyrics }); |
| 72 | + } else if (plainLyrics) { |
| 73 | + setLyric({ plain: plainLyrics }); |
128 | 74 | } else { |
129 | | - setLyric({ plain: "No Lyrics Found\nSorry, we couldn't find lyrics for this song." }); |
| 75 | + setLyric({ plain: ERROR_MESSAGES.EMPTY_LYRICS }); |
130 | 76 | } |
131 | 77 | } catch (error) { |
132 | | - console.error("Error fetching lyrics in LyricsHandler:", error); |
133 | | - setLyric({ plain: "Could not fetch lyrics. Please try again." }); |
| 78 | + console.error('Error fetching lyrics:', error); |
| 79 | + setLyric({ plain: ERROR_MESSAGES.FETCH_ERROR }); |
134 | 80 | } finally { |
135 | | - setLoading(false); |
136 | | - } |
137 | | - }; |
138 | | - |
139 | | - // Effect to clear lyrics when track changes if dialog is not open, to prevent stale lyrics briefly showing |
140 | | - useEffect(() => { |
141 | | - if (!ShowDailog) { |
142 | | - setLyric(null); |
| 81 | + setIsLoading(false); |
143 | 82 | } |
144 | | - }, [currentPlayingTrack?.id, ShowDailog]); |
| 83 | + }, [currentPlayingTrack, isOffline]); |
145 | 84 |
|
146 | 85 | return ( |
147 | 86 | <> |
148 | 87 | <GetLyricsButton onPress={fetchLyrics} /> |
149 | 88 | <ShowLyrics |
150 | | - ShowDailog={ShowDailog} |
151 | | - Loading={Loading} |
152 | | - Lyric={Lyric} |
153 | | - setShowDailog={setShowDailog} |
| 89 | + ShowDailog={showDialog} |
| 90 | + Loading={isLoading} |
| 91 | + Lyric={lyric} |
| 92 | + setShowDailog={setShowDialog} |
154 | 93 | currentArtworkSource={currentArtworkSource} |
155 | 94 | /> |
156 | 95 | </> |
157 | 96 | ); |
158 | 97 | }; |
| 98 | + |
| 99 | +// Add prop type validation if needed |
| 100 | +// LyricsHandler.propTypes = { |
| 101 | +// currentPlayingTrack: PropTypes.shape({ |
| 102 | +// title: PropTypes.string, |
| 103 | +// artist: PropTypes.string, |
| 104 | +// id: PropTypes.string, |
| 105 | +// }), |
| 106 | +// isOffline: PropTypes.bool, |
| 107 | +// onLyricsVisibilityChange: PropTypes.func, |
| 108 | +// currentArtworkSource: PropTypes.any, |
| 109 | +// }; |
0 commit comments