spotify integration works

This commit is contained in:
Leon Astner 2025-08-02 04:48:34 +02:00
parent f70fe3cdd1
commit 025eee7644
11 changed files with 1637 additions and 717 deletions

View file

@ -0,0 +1,204 @@
import 'package:flutter/foundation.dart';
import '../models/spotify_track.dart';
import '../services/spotify_service.dart';
class QueueItem {
final SpotifyTrack track;
int votes;
bool userVoted;
final DateTime addedAt;
QueueItem({
required this.track,
this.votes = 1,
this.userVoted = true,
DateTime? addedAt,
}) : addedAt = addedAt ?? DateTime.now();
Map<String, dynamic> toJson() => {
'id': track.id,
'title': track.name,
'artist': track.artistNames,
'votes': votes,
'userVoted': userVoted,
'duration': track.duration,
'imageUrl': track.imageUrl,
'addedAt': addedAt.toIso8601String(),
};
}
class MusicQueueService extends ChangeNotifier {
static final MusicQueueService _instance = MusicQueueService._internal();
factory MusicQueueService() => _instance;
MusicQueueService._internal();
final SpotifyService _spotifyService = SpotifyService();
// Current playing track
SpotifyTrack? _currentTrack;
bool _isPlaying = false;
double _progress = 0.0;
// Queue management
final List<QueueItem> _queue = [];
// Recently played
final List<SpotifyTrack> _recentlyPlayed = [];
// Getters
SpotifyTrack? get currentTrack => _currentTrack;
bool get isPlaying => _isPlaying;
double get progress => _progress;
List<QueueItem> get queue => List.unmodifiable(_queue);
List<SpotifyTrack> get recentlyPlayed => List.unmodifiable(_recentlyPlayed);
// Queue operations
void addToQueue(SpotifyTrack track) {
// Check if track is already in queue
final existingIndex = _queue.indexWhere((item) => item.track.id == track.id);
if (existingIndex != -1) {
// If track exists, upvote it
upvote(existingIndex);
} else {
// Add new track to queue
final queueItem = QueueItem(track: track);
_queue.add(queueItem);
_sortQueue();
notifyListeners();
print('Added "${track.name}" by ${track.artistNames} to queue');
}
}
void upvote(int index) {
if (index >= 0 && index < _queue.length) {
_queue[index].votes++;
if (!_queue[index].userVoted) {
_queue[index].userVoted = true;
}
_sortQueue();
notifyListeners();
}
}
void downvote(int index) {
if (index >= 0 && index < _queue.length) {
if (_queue[index].votes > 0) {
_queue[index].votes--;
}
_sortQueue();
notifyListeners();
}
}
void _sortQueue() {
_queue.sort((a, b) {
// First sort by votes (descending)
final voteComparison = b.votes.compareTo(a.votes);
if (voteComparison != 0) return voteComparison;
// If votes are equal, sort by time added (ascending - first come first serve)
return a.addedAt.compareTo(b.addedAt);
});
}
// Playback simulation
void playNext() {
if (_queue.isNotEmpty) {
final nextItem = _queue.removeAt(0);
_currentTrack = nextItem.track;
_isPlaying = true;
_progress = 0.0;
// Add to recently played
_recentlyPlayed.insert(0, nextItem.track);
if (_recentlyPlayed.length > 10) {
_recentlyPlayed.removeLast();
}
notifyListeners();
print('Now playing: ${_currentTrack!.name} by ${_currentTrack!.artistNames}');
// Simulate track progress
_simulatePlayback();
}
}
void togglePlayPause() {
_isPlaying = !_isPlaying;
notifyListeners();
}
void skipTrack() {
playNext();
}
void _simulatePlayback() {
if (_currentTrack == null) return;
// Simulate track progress over time
Future.delayed(const Duration(seconds: 1), () {
if (_isPlaying && _currentTrack != null) {
_progress += 1.0 / (_currentTrack!.durationMs / 1000);
if (_progress >= 1.0) {
// Track finished, play next
playNext();
} else {
notifyListeners();
_simulatePlayback();
}
}
});
}
// Initialize with some popular tracks
Future<void> initializeQueue() async {
if (_queue.isEmpty && _currentTrack == null) {
try {
final popularTracks = await _spotifyService.getPopularTracks();
for (final track in popularTracks.take(4)) {
final queueItem = QueueItem(
track: track,
votes: 10 - popularTracks.indexOf(track) * 2, // Decreasing votes
userVoted: false,
);
_queue.add(queueItem);
}
// Start playing the first track
if (_queue.isNotEmpty) {
playNext();
}
notifyListeners();
} catch (e) {
print('Error initializing queue: $e');
}
}
}
// Search functionality
Future<List<SpotifyTrack>> searchTracks(String query) async {
return await _spotifyService.searchTracks(query, limit: 20);
}
// Get queue as JSON for display
List<Map<String, dynamic>> get queueAsJson {
return _queue.map((item) => item.toJson()).toList();
}
// Get current track info for display
Map<String, dynamic>? get currentTrackInfo {
if (_currentTrack == null) return null;
return {
'title': _currentTrack!.name,
'artist': _currentTrack!.artistNames,
'album': _currentTrack!.album.name,
'imageUrl': _currentTrack!.imageUrl,
'duration': _currentTrack!.duration,
'progress': _progress,
'isPlaying': _isPlaying,
};
}
}

View file

@ -1,20 +1,48 @@
// 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 {
static const String _clientId = 'YOUR_SPOTIFY_CLIENT_ID'; // You'll need to get this from Spotify Developer Console
static const String _clientSecret = 'YOUR_SPOTIFY_CLIENT_SECRET'; // You'll need to get this from Spotify Developer Console
// 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<void> _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),
@ -44,6 +72,11 @@ class SpotifyService {
}
Future<void> _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();
@ -59,8 +92,9 @@ class SpotifyService {
try {
await _ensureValidToken();
if (_accessToken == null) {
// Return demo data if no token available
// If no valid credentials or token, use demo data
if (!_hasValidCredentials || _accessToken == null) {
print('Using demo data for search: $query');
return _getDemoTracks(query);
}
@ -75,6 +109,7 @@ class SpotifyService {
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
@ -96,7 +131,8 @@ class SpotifyService {
try {
await _ensureValidToken();
if (_accessToken == null) {
if (!_hasValidCredentials || _accessToken == null) {
print('Using demo popular tracks');
return _getDemoPopularTracks();
}
@ -170,12 +206,19 @@ class SpotifyService {
);
}
// Method to initialize with your Spotify credentials
static void setCredentials(String clientId, String clientSecret) {
// In a real app, you'd store these securely
// For demo purposes, we'll use the demo data
print('Spotify credentials would be set here in a real app');
print('Client ID: $clientId');
print('For demo purposes, using mock data instead');
// 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)';
}
}
}