icon things

This commit is contained in:
Leon Astner 2025-08-02 07:51:04 +02:00
parent a91654df03
commit 8bc45ad6fd
50 changed files with 592 additions and 213 deletions

View file

@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:network_info_plus/network_info_plus.dart';
import 'package:multicast_dns/multicast_dns.dart';
import 'music_queue_service.dart';
class NetworkUser {
final String id;
@ -15,6 +16,11 @@ class NetworkUser {
final int votes;
bool isOnline;
DateTime lastSeen;
String? currentTrackId;
String? currentTrackName;
String? currentArtist;
String? currentTrackImage;
bool isListening;
NetworkUser({
required this.id,
@ -24,6 +30,11 @@ class NetworkUser {
this.votes = 0,
this.isOnline = true,
DateTime? lastSeen,
this.currentTrackId,
this.currentTrackName,
this.currentArtist,
this.currentTrackImage,
this.isListening = false,
}) : lastSeen = lastSeen ?? DateTime.now();
Map<String, dynamic> toJson() => {
@ -34,6 +45,11 @@ class NetworkUser {
'votes': votes,
'isOnline': isOnline,
'lastSeen': lastSeen.toIso8601String(),
'currentTrackId': currentTrackId,
'currentTrackName': currentTrackName,
'currentArtist': currentArtist,
'currentTrackImage': currentTrackImage,
'isListening': isListening,
};
factory NetworkUser.fromJson(Map<String, dynamic> json) => NetworkUser(
@ -44,6 +60,11 @@ class NetworkUser {
votes: json['votes'] ?? 0,
isOnline: json['isOnline'] ?? true,
lastSeen: DateTime.parse(json['lastSeen']),
currentTrackId: json['currentTrackId'],
currentTrackName: json['currentTrackName'],
currentArtist: json['currentArtist'],
currentTrackImage: json['currentTrackImage'],
isListening: json['isListening'] ?? false,
);
}
@ -66,6 +87,7 @@ class NetworkGroupService extends ChangeNotifier {
final Map<String, NetworkUser> _networkUsers = {};
late NetworkUser _currentUser;
MusicQueueService? _musicService;
// Getters
bool get isConnectedToWifi => _isConnectedToWifi;
@ -79,6 +101,8 @@ class NetworkGroupService extends ChangeNotifier {
NetworkGroupService() {
_initializeCurrentUser();
_startNetworkMonitoring();
// Initialize music service reference
_musicService = MusicQueueService();
}
void _initializeCurrentUser() {
@ -232,6 +256,8 @@ class NetworkGroupService extends ChangeNotifier {
response.headers.set('Access-Control-Allow-Origin', '*');
if (request.method == 'GET' && request.uri.path == '/user') {
// Update current user with latest listening info before sending
await _updateCurrentUserListeningInfo();
// Return current user info
response.write(jsonEncode(_currentUser.toJson()));
} else if (request.method == 'POST' && request.uri.path == '/heartbeat') {
@ -244,6 +270,41 @@ class NetworkGroupService extends ChangeNotifier {
notifyListeners();
response.write(jsonEncode({'status': 'ok'}));
} else if (request.method == 'POST' && request.uri.path == '/join-session') {
// Handle request to join this user's listening session
final body = await utf8.decoder.bind(request).join();
// Parse request data for future use (logging, analytics, etc.)
jsonDecode(body);
// Get current track info to send back
final currentTrackInfo = _musicService?.currentTrackInfo;
if (currentTrackInfo != null) {
response.write(jsonEncode({
'status': 'ok',
'trackInfo': currentTrackInfo,
'message': 'Successfully joined listening session'
}));
} else {
response.write(jsonEncode({
'status': 'no_track',
'message': 'No track currently playing'
}));
}
} else if (request.method == 'GET' && request.uri.path == '/current-track') {
// Get current track info without joining
await _updateCurrentUserListeningInfo();
final currentTrackInfo = _musicService?.currentTrackInfo;
if (currentTrackInfo != null) {
response.write(jsonEncode({
'status': 'ok',
'trackInfo': currentTrackInfo
}));
} else {
response.write(jsonEncode({
'status': 'no_track',
'message': 'No track currently playing'
}));
}
} else {
response.statusCode = 404;
response.write(jsonEncode({'error': 'Not found'}));
@ -395,6 +456,39 @@ class NetworkGroupService extends ChangeNotifier {
}
}
Future<void> _updateCurrentUserListeningInfo() async {
final currentTrackInfo = _musicService?.currentTrackInfo;
final currentTrack = _musicService?.currentTrack;
if (currentTrack != null && currentTrackInfo != null) {
_currentUser = NetworkUser(
id: _currentUser.id,
name: _currentUser.name,
ipAddress: _currentUser.ipAddress,
joinedAt: _currentUser.joinedAt,
votes: _currentUser.votes,
isOnline: true,
currentTrackId: currentTrack.id,
currentTrackName: currentTrack.name,
currentArtist: currentTrack.artistNames,
currentTrackImage: currentTrack.imageUrl,
isListening: currentTrackInfo['isPlaying'] ?? false,
);
} else {
_currentUser = NetworkUser(
id: _currentUser.id,
name: _currentUser.name,
ipAddress: _currentUser.ipAddress,
joinedAt: _currentUser.joinedAt,
votes: _currentUser.votes,
isOnline: true,
isListening: false,
);
}
_networkUsers[_currentUser.id] = _currentUser;
}
// Public methods for UI interaction
Future<void> refreshNetwork() async {
await _checkConnectivity();
@ -426,6 +520,45 @@ class NetworkGroupService extends ChangeNotifier {
return 'Connected to $_currentNetworkName';
}
// Join another user's listening session
Future<bool> joinListeningSession(NetworkUser user) async {
if (!user.isListening || user.currentTrackId == null) {
return false;
}
try {
final client = HttpClient();
client.connectionTimeout = const Duration(seconds: 5);
final request = await client.postUrl(
Uri.parse('http://${user.ipAddress}:$_discoveryPort/join-session')
);
request.headers.set('Content-Type', 'application/json');
request.write(jsonEncode({'userId': _currentUser.id}));
final response = await request.close().timeout(const Duration(seconds: 5));
if (response.statusCode == 200) {
final body = await utf8.decoder.bind(response).join();
final responseData = jsonDecode(body);
if (responseData['status'] == 'ok' && responseData['trackInfo'] != null) {
// Here you could sync the track with your local player
// For now, we'll just return success
return true;
}
}
client.close();
return false;
} catch (e) {
if (kDebugMode) {
print('Error joining listening session: $e');
}
return false;
}
}
// Demo methods for testing
void simulateNetworkConnection() {
_isConnectedToWifi = true;

View file

@ -34,7 +34,13 @@ class SpamProtectionService extends ChangeNotifier {
return false;
}
// Check cooldown
// Allow first vote without cooldown for smooth user experience
final votes = _userVotes[userId] ?? [];
if (votes.isEmpty) {
return true; // First vote is always allowed
}
// Check cooldown for subsequent votes
if (_isOnCooldown(userId, _lastVoteTime, voteCooldown)) {
return false;
}
@ -54,7 +60,13 @@ class SpamProtectionService extends ChangeNotifier {
return false;
}
// Check cooldown
// Allow first suggestion without cooldown for smooth user experience
final suggestions = _userSuggestions[userId] ?? [];
if (suggestions.isEmpty) {
return true; // First suggestion is always allowed
}
// Check cooldown for subsequent suggestions
if (_isOnCooldown(userId, _lastSuggestionTime, suggestionCooldown)) {
return false;
}