// Spotify API Credentials // // SETUP INSTRUCTIONS: // 1. Go to https://developer.spotify.com/dashboard // 2. Log in with your Spotify account // 3. Create a new app called "SleepySound" // 4. Copy your Client ID and Client Secret below // 5. Save this file // // SECURITY NOTE: Never commit real credentials to version control! // For production, use environment variables or secure storage. import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import '../models/spotify_track.dart'; import 'SPOTIFY_SECRET.dart'; class SpotifyService { // Load credentials from the secret file static String get _clientId => SpotifyCredentials.clientId; static String get _clientSecret => SpotifyCredentials.clientSecret; static const String _baseUrl = 'https://api.spotify.com/v1'; static const String _authUrl = 'https://accounts.spotify.com/api/token'; String? _accessToken; // Check if valid credentials are provided bool get _hasValidCredentials => _clientId != 'YOUR_SPOTIFY_CLIENT_ID' && _clientSecret != 'YOUR_SPOTIFY_CLIENT_SECRET' && _clientId.isNotEmpty && _clientSecret.isNotEmpty; // For demo purposes, we'll use Client Credentials flow (no user login required) // In a real app, you'd want to implement Authorization Code flow for user-specific features Future _getAccessToken() async { // Check if we have valid credentials first if (!_hasValidCredentials) { print('No valid Spotify credentials found. Using demo data.'); return; } try { final response = await http.post( Uri.parse(_authUrl), headers: { 'Authorization': 'Basic ${base64Encode(utf8.encode('$_clientId:$_clientSecret'))}', 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'grant_type=client_credentials', ); if (response.statusCode == 200) { final data = json.decode(response.body); _accessToken = data['access_token']; // Save token to shared preferences final prefs = await SharedPreferences.getInstance(); await prefs.setString('spotify_access_token', _accessToken!); print('Spotify access token obtained successfully'); } else { print('Failed to get Spotify access token: ${response.statusCode}'); print('Response body: ${response.body}'); } } catch (e) { print('Error getting Spotify access token: $e'); } } Future _ensureValidToken() async { // If no valid credentials, skip token generation if (!_hasValidCredentials) { return; } if (_accessToken == null) { // Try to load from shared preferences first final prefs = await SharedPreferences.getInstance(); _accessToken = prefs.getString('spotify_access_token'); if (_accessToken == null) { await _getAccessToken(); } } } Future> searchTracks(String query, {int limit = 20}) async { try { await _ensureValidToken(); // If no valid credentials or token, use demo data if (!_hasValidCredentials || _accessToken == null) { print('Using demo data for search: $query'); return _getDemoTracks(query); } final encodedQuery = Uri.encodeQueryComponent(query); final response = await http.get( Uri.parse('$_baseUrl/search?q=$encodedQuery&type=track&limit=$limit'), headers: { 'Authorization': 'Bearer $_accessToken', }, ); if (response.statusCode == 200) { final data = json.decode(response.body); final searchResponse = SpotifySearchResponse.fromJson(data); print('Found ${searchResponse.tracks.items.length} tracks from Spotify API'); return searchResponse.tracks.items; } else if (response.statusCode == 401) { // Token expired, get a new one _accessToken = null; await _getAccessToken(); return searchTracks(query, limit: limit); // Retry } else { print('Spotify search failed: ${response.statusCode}'); print('Response body: ${response.body}'); return _getDemoTracks(query); } } catch (e) { print('Error searching Spotify: $e'); return _getDemoTracks(query); } } Future> getPopularTracks({String genre = 'chill'}) async { try { await _ensureValidToken(); if (!_hasValidCredentials || _accessToken == null) { print('Using demo popular tracks'); return _getDemoPopularTracks(); } // Search for popular tracks in the genre final response = await http.get( Uri.parse('$_baseUrl/search?q=genre:$genre&type=track&limit=10'), headers: { 'Authorization': 'Bearer $_accessToken', }, ); if (response.statusCode == 200) { final data = json.decode(response.body); final searchResponse = SpotifySearchResponse.fromJson(data); return searchResponse.tracks.items; } else { return _getDemoPopularTracks(); } } catch (e) { print('Error getting popular tracks: $e'); return _getDemoPopularTracks(); } } // Demo data for when Spotify API is not available List _getDemoTracks(String query) { final demoTracks = [ _createDemoTrack('1', 'Tropical House Cruises', 'Kygo', 'Cloud Nine', 'https://i.scdn.co/image/tropical'), _createDemoTrack('2', 'Summer Breeze', 'Seeb', 'Summer Hits', 'https://i.scdn.co/image/summer'), _createDemoTrack('3', 'Relaxing Waves', 'Chillhop Music', 'Chill Collection', 'https://i.scdn.co/image/waves'), _createDemoTrack('4', 'Sunset Vibes', 'Odesza', 'In Return', 'https://i.scdn.co/image/sunset'), _createDemoTrack('5', 'Ocean Dreams', 'Emancipator', 'Soon It Will Be Cold Enough', 'https://i.scdn.co/image/ocean'), ]; // Filter based on query if (query.toLowerCase().contains('tropical') || query.toLowerCase().contains('kygo')) { return [demoTracks[0]]; } else if (query.toLowerCase().contains('summer')) { return [demoTracks[1]]; } else if (query.toLowerCase().contains('chill') || query.toLowerCase().contains('relax')) { return [demoTracks[2], demoTracks[4]]; } else if (query.toLowerCase().contains('sunset')) { return [demoTracks[3]]; } return demoTracks; } List _getDemoPopularTracks() { return [ _createDemoTrack('pop1', 'Ocean Breeze', 'Lofi Dreams', 'Summer Collection', 'https://i.scdn.co/image/ocean'), _createDemoTrack('pop2', 'Sunset Melody', 'Acoustic Soul', 'Peaceful Moments', 'https://i.scdn.co/image/sunset'), _createDemoTrack('pop3', 'Peaceful Waters', 'Nature Sounds', 'Tranquil Vibes', 'https://i.scdn.co/image/water'), _createDemoTrack('pop4', 'Summer Nights', 'Chill Vibes', 'Evening Sessions', 'https://i.scdn.co/image/night'), ]; } SpotifyTrack _createDemoTrack(String id, String name, String artistName, String albumName, String imageUrl) { return SpotifyTrack( id: id, name: name, artists: [SpotifyArtist(id: 'artist_$id', name: artistName)], album: SpotifyAlbum( id: 'album_$id', name: albumName, images: [SpotifyImage(height: 640, width: 640, url: imageUrl)], ), durationMs: 210000 + (id.hashCode % 120000), // Random duration between 3:30 and 5:30 externalUrls: {'spotify': 'https://open.spotify.com/track/$id'}, previewUrl: null, ); } // Method to check if Spotify API is properly configured static bool get isConfigured => SpotifyCredentials.clientId != 'YOUR_SPOTIFY_CLIENT_ID' && SpotifyCredentials.clientSecret != 'YOUR_SPOTIFY_CLIENT_SECRET' && SpotifyCredentials.clientId.isNotEmpty && SpotifyCredentials.clientSecret.isNotEmpty; // Method to get configuration status for UI display static String get configurationStatus { if (isConfigured) { return 'Spotify API configured ✓'; } else { return 'Using demo data (Spotify not configured)'; } } }