diff --git a/CHALLENGE_2/sleepysound/android/app/build.gradle.kts b/CHALLENGE_2/sleepysound/android/app/build.gradle.kts
index e2da798..7e83ad8 100644
--- a/CHALLENGE_2/sleepysound/android/app/build.gradle.kts
+++ b/CHALLENGE_2/sleepysound/android/app/build.gradle.kts
@@ -8,7 +8,7 @@ plugins {
android {
namespace = "com.example.sleepysound"
compileSdk = flutter.compileSdkVersion
- ndkVersion = flutter.ndkVersion
+ ndkVersion = "27.0.12077973"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..704eeb6
Binary files /dev/null and b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..27f8dcd
Binary files /dev/null and b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..f34ea95
Binary files /dev/null and b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..5007b56
Binary files /dev/null and b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..bfcc770
Binary files /dev/null and b/CHALLENGE_2/sleepysound/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..5f349f7
--- /dev/null
+++ b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index db77bb4..3e80a2d 100644
Binary files a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 17987b7..6bbfdac 100644
Binary files a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 09d4391..aa53945 100644
Binary files a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index d5f1c8d..421186e 100644
Binary files a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 4d6372e..f087383 100644
Binary files a/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/CHALLENGE_2/sleepysound/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/CHALLENGE_2/sleepysound/android/app/src/main/res/values/colors.xml b/CHALLENGE_2/sleepysound/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3b44edc
--- /dev/null
+++ b/CHALLENGE_2/sleepysound/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #121212
+
\ No newline at end of file
diff --git a/CHALLENGE_2/sleepysound/assets/icons/app_icon.png b/CHALLENGE_2/sleepysound/assets/icons/app_icon.png
new file mode 100644
index 0000000..1fd3697
Binary files /dev/null and b/CHALLENGE_2/sleepysound/assets/icons/app_icon.png differ
diff --git a/CHALLENGE_2/sleepysound/assets/icons/app_icon_foreground.png b/CHALLENGE_2/sleepysound/assets/icons/app_icon_foreground.png
new file mode 100644
index 0000000..c8713ac
Binary files /dev/null and b/CHALLENGE_2/sleepysound/assets/icons/app_icon_foreground.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner.xcodeproj/project.pbxproj b/CHALLENGE_2/sleepysound/ios/Runner.xcodeproj/project.pbxproj
index f9b1ec5..bfa417c 100644
--- a/CHALLENGE_2/sleepysound/ios/Runner.xcodeproj/project.pbxproj
+++ b/CHALLENGE_2/sleepysound/ios/Runner.xcodeproj/project.pbxproj
@@ -427,7 +427,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -484,7 +484,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
index dc9ada4..b0c09f7 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
index 7353c41..3a08db0 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
index 797d452..5cbcf61 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
index 6ed2d93..1e78f97 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
index 4cd7b00..37c653f 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
index fe73094..3875cf2 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
index 321773c..4dd58ad 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
index 797d452..5cbcf61 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
index 502f463..a2269a5 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
index 0ec3034..082a070 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
new file mode 100644
index 0000000..a729965
Binary files /dev/null and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
new file mode 100644
index 0000000..487e67d
Binary files /dev/null and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
new file mode 100644
index 0000000..0db5bbf
Binary files /dev/null and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
new file mode 100644
index 0000000..3ad426d
Binary files /dev/null and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
index 0ec3034..082a070 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
index e9f5fea..86f6399 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
new file mode 100644
index 0000000..3e80a2d
Binary files /dev/null and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
new file mode 100644
index 0000000..421186e
Binary files /dev/null and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
index 84ac32a..c8db051 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
index 8953cba..fd632fe 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
index 0467bf1..837bbac 100644
Binary files a/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/CHALLENGE_2/sleepysound/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/CHALLENGE_2/sleepysound/lib/main.dart b/CHALLENGE_2/sleepysound/lib/main.dart
index 0ba6ad7..117b8e6 100644
--- a/CHALLENGE_2/sleepysound/lib/main.dart
+++ b/CHALLENGE_2/sleepysound/lib/main.dart
@@ -26,7 +26,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (context) => AudioService()),
],
child: MaterialApp(
- title: 'SleepySound',
+ title: 'LidoSound',
theme: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
diff --git a/CHALLENGE_2/sleepysound/lib/pages/group_page.dart b/CHALLENGE_2/sleepysound/lib/pages/group_page.dart
index 36dd259..765df62 100644
--- a/CHALLENGE_2/sleepysound/lib/pages/group_page.dart
+++ b/CHALLENGE_2/sleepysound/lib/pages/group_page.dart
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../services/network_group_service.dart';
-import '../widgets/network_demo_widget.dart';
class GroupPage extends StatefulWidget {
const GroupPage({super.key});
@@ -172,9 +171,6 @@ class _GroupPageState extends State {
const SizedBox(height: 25),
- // Demo Widget
- NetworkDemoWidget(networkService: networkService),
-
// Network Users Section
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -330,13 +326,34 @@ class _GroupPageState extends State {
const SizedBox(height: 4),
Row(
children: [
- Text(
- 'Joined ${_formatDuration(DateTime.now().difference(user.joinedAt))} ago',
- style: const TextStyle(
- color: Colors.grey,
- fontSize: 12,
+ if (user.isListening && user.currentTrackName != null) ...[
+ const Icon(
+ Icons.music_note,
+ color: Color(0xFF6366F1),
+ size: 14,
),
- ),
+ const SizedBox(width: 4),
+ Expanded(
+ child: Text(
+ 'Listening to "${user.currentTrackName}" by ${user.currentArtist}',
+ style: const TextStyle(
+ color: Color(0xFF6366F1),
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ ] else ...[
+ Text(
+ 'Joined ${_formatDuration(DateTime.now().difference(user.joinedAt))} ago',
+ style: const TextStyle(
+ color: Colors.grey,
+ fontSize: 12,
+ ),
+ ),
+ ],
if (!user.isOnline) ...[
const Text(
' • ',
@@ -366,20 +383,54 @@ class _GroupPageState extends State {
],
),
),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
- decoration: BoxDecoration(
- color: const Color(0xFF6366F1).withOpacity(0.2),
- borderRadius: BorderRadius.circular(10),
- ),
- child: Text(
- '${user.votes} votes',
- style: const TextStyle(
- color: Color(0xFF6366F1),
- fontSize: 11,
- fontWeight: FontWeight.bold,
+ Column(
+ children: [
+ if (user.isListening && !isCurrentUser) ...[
+ // Join Listening Session Button
+ IconButton(
+ onPressed: () async {
+ final success = await networkService.joinListeningSession(user);
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(
+ success
+ ? 'Joined ${user.name}\'s listening session! 🎵'
+ : 'Failed to join listening session',
+ ),
+ backgroundColor: success
+ ? const Color(0xFF22C55E)
+ : const Color(0xFFEF4444),
+ duration: const Duration(seconds: 3),
+ ),
+ );
+ }
+ },
+ icon: const Icon(
+ Icons.headphones,
+ color: Color(0xFF6366F1),
+ size: 20,
+ ),
+ tooltip: 'Join listening session',
+ ),
+ const SizedBox(height: 4),
+ ],
+ Container(
+ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+ decoration: BoxDecoration(
+ color: const Color(0xFF6366F1).withOpacity(0.2),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Text(
+ '${user.votes} votes',
+ style: const TextStyle(
+ color: Color(0xFF6366F1),
+ fontSize: 11,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
),
- ),
+ ],
),
],
),
diff --git a/CHALLENGE_2/sleepysound/lib/pages/voting_page.dart b/CHALLENGE_2/sleepysound/lib/pages/voting_page.dart
index bbcbcc8..647e9fb 100644
--- a/CHALLENGE_2/sleepysound/lib/pages/voting_page.dart
+++ b/CHALLENGE_2/sleepysound/lib/pages/voting_page.dart
@@ -15,14 +15,188 @@ class VotingPage extends StatefulWidget {
class _VotingPageState extends State {
final TextEditingController _searchController = TextEditingController();
+ final FocusNode _searchFocusNode = FocusNode();
List _searchResults = [];
bool _isLoading = false;
String _statusMessage = '';
+
+ final LayerLink _layerLink = LayerLink();
+ OverlayEntry? _overlayEntry;
@override
void initState() {
super.initState();
_loadInitialQueue();
+ _searchFocusNode.addListener(_onSearchFocusChange);
+ }
+
+ @override
+ void dispose() {
+ _hideSearchOverlay();
+ _searchController.dispose();
+ _searchFocusNode.dispose();
+ super.dispose();
+ }
+
+ void _onSearchFocusChange() {
+ if (_searchFocusNode.hasFocus && _searchResults.isNotEmpty) {
+ _showSearchOverlay();
+ } else if (!_searchFocusNode.hasFocus) {
+ // Delay hiding to allow for taps on results
+ Future.delayed(const Duration(milliseconds: 150), () {
+ _hideSearchOverlay();
+ });
+ }
+ }
+
+ void _showSearchOverlay() {
+ if (_overlayEntry != null) return;
+
+ _overlayEntry = _createOverlayEntry();
+ Overlay.of(context).insert(_overlayEntry!);
+ }
+
+ void _hideSearchOverlay() {
+ _overlayEntry?.remove();
+ _overlayEntry = null;
+ }
+
+ OverlayEntry _createOverlayEntry() {
+ RenderBox renderBox = context.findRenderObject() as RenderBox;
+ var size = renderBox.size;
+ var offset = renderBox.localToGlobal(Offset.zero);
+
+ return OverlayEntry(
+ builder: (context) => Positioned(
+ left: offset.dx + 20,
+ top: offset.dy + 200, // Adjust based on search field position
+ width: size.width - 40,
+ child: CompositedTransformFollower(
+ link: _layerLink,
+ showWhenUnlinked: false,
+ child: Material(
+ elevation: 8,
+ borderRadius: BorderRadius.circular(12),
+ color: const Color(0xFF1E1E1E),
+ child: Container(
+ constraints: const BoxConstraints(maxHeight: 300),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12),
+ border: Border.all(color: const Color(0xFF6366F1).withOpacity(0.3)),
+ ),
+ child: _buildSearchResultsOverlay(),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSearchResultsOverlay() {
+ if (_searchResults.isEmpty) {
+ return Container(
+ padding: const EdgeInsets.all(20),
+ child: const Text(
+ 'No results found',
+ style: TextStyle(color: Colors.grey),
+ textAlign: TextAlign.center,
+ ),
+ );
+ }
+
+ return ListView.builder(
+ shrinkWrap: true,
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ itemCount: _searchResults.length,
+ itemBuilder: (context, index) {
+ final track = _searchResults[index];
+ return _buildSearchResultItem(track, index);
+ },
+ );
+ }
+
+ Widget _buildSearchResultItem(SpotifyTrack track, int index) {
+ return InkWell(
+ onTap: () {
+ _addToQueue(track);
+ _hideSearchOverlay();
+ _searchController.clear();
+ _searchFocusNode.unfocus();
+ },
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+ child: Row(
+ children: [
+ // Album Art
+ Container(
+ width: 40,
+ height: 40,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(6),
+ color: const Color(0xFF2A2A2A),
+ ),
+ child: track.album.images.isNotEmpty
+ ? ClipRRect(
+ borderRadius: BorderRadius.circular(6),
+ child: Image.network(
+ track.album.images.first.url,
+ fit: BoxFit.cover,
+ errorBuilder: (context, error, stackTrace) {
+ return const Icon(
+ Icons.music_note,
+ color: Colors.grey,
+ size: 16,
+ );
+ },
+ ),
+ )
+ : const Icon(
+ Icons.music_note,
+ color: Colors.grey,
+ size: 16,
+ ),
+ ),
+ const SizedBox(width: 12),
+
+ // Track Info
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ track.name,
+ style: const TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.w500,
+ fontSize: 14,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ const SizedBox(height: 2),
+ Text(
+ track.artists.map((a) => a.name).join(', '),
+ style: const TextStyle(
+ color: Colors.grey,
+ fontSize: 12,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
+ ),
+
+ // Add Icon
+ const Icon(
+ Icons.add_circle_outline,
+ color: Color(0xFF6366F1),
+ size: 20,
+ ),
+ ],
+ ),
+ ),
+ );
}
Future _loadInitialQueue() async {
@@ -31,7 +205,14 @@ class _VotingPageState extends State {
}
Future _searchSpotify(String query) async {
- if (query.isEmpty) return;
+ if (query.isEmpty) {
+ setState(() {
+ _searchResults = [];
+ _statusMessage = '';
+ });
+ _hideSearchOverlay();
+ return;
+ }
// Check if search query is appropriate
if (!GenreFilterService.isSearchQueryAppropriate(query)) {
@@ -41,6 +222,7 @@ class _VotingPageState extends State {
_statusMessage = 'Search term not suitable for the peaceful Lido atmosphere. Try: ${suggestions.join(', ')}';
_searchResults = [];
});
+ _hideSearchOverlay();
return;
}
@@ -57,6 +239,7 @@ class _VotingPageState extends State {
_statusMessage = blockMessage ?? 'Please wait $cooldown seconds before searching again.';
_searchResults = [];
});
+ _hideSearchOverlay();
return;
}
@@ -69,31 +252,35 @@ class _VotingPageState extends State {
final queueService = Provider.of(context, listen: false);
final results = await queueService.searchTracks(query);
- // Filter results based on genre appropriateness
- final filteredResults = GenreFilterService.filterSearchResults(results);
+ // No filtering on search results - let users see all tracks
+ // Filtering only happens when adding to queue to maintain atmosphere
// Record the suggestion attempt
spamService.recordSuggestion(userId);
setState(() {
- _searchResults = filteredResults;
+ _searchResults = results;
_isLoading = false;
- if (filteredResults.isEmpty && results.isNotEmpty) {
- _statusMessage = 'No tracks found that match the peaceful Lido atmosphere. Try searching for chill, ambient, or relaxing music.';
- } else if (filteredResults.isEmpty) {
+ if (results.isEmpty) {
_statusMessage = 'No tracks found for "$query"';
} else {
- final filtered = results.length - filteredResults.length;
- _statusMessage = 'Found ${filteredResults.length} tracks' +
- (filtered > 0 ? ' ($filtered filtered for atmosphere)' : '');
+ _statusMessage = 'Found ${results.length} tracks';
}
});
+
+ // Show overlay if we have results and search field is focused
+ if (results.isNotEmpty && _searchFocusNode.hasFocus) {
+ _showSearchOverlay();
+ } else {
+ _hideSearchOverlay();
+ }
} catch (e) {
setState(() {
_isLoading = false;
_statusMessage = 'Search failed: ${e.toString()}';
_searchResults = [];
});
+ _hideSearchOverlay();
}
}
@@ -202,34 +389,74 @@ class _VotingPageState extends State {
const SizedBox(height: 20),
// Search Bar
- TextField(
- controller: _searchController,
- style: const TextStyle(color: Colors.white),
- decoration: InputDecoration(
- hintText: 'Search for songs, artists, albums...',
- hintStyle: const TextStyle(color: Colors.grey),
- prefixIcon: const Icon(Icons.search, color: Colors.grey),
- suffixIcon: _isLoading
- ? const Padding(
- padding: EdgeInsets.all(12),
- child: SizedBox(
- width: 20,
- height: 20,
- child: CircularProgressIndicator(
- strokeWidth: 2,
- valueColor: AlwaysStoppedAnimation(Color(0xFF6366F1)),
+ CompositedTransformTarget(
+ link: _layerLink,
+ child: TextField(
+ controller: _searchController,
+ focusNode: _searchFocusNode,
+ style: const TextStyle(color: Colors.white),
+ decoration: InputDecoration(
+ hintText: 'Search for songs, artists, albums...',
+ hintStyle: const TextStyle(color: Colors.grey),
+ prefixIcon: const Icon(Icons.search, color: Colors.grey),
+ suffixIcon: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (_isLoading)
+ const Padding(
+ padding: EdgeInsets.all(12),
+ child: SizedBox(
+ width: 20,
+ height: 20,
+ child: CircularProgressIndicator(
+ strokeWidth: 2,
+ valueColor: AlwaysStoppedAnimation(Color(0xFF6366F1)),
+ ),
),
),
- )
- : null,
- filled: true,
- fillColor: const Color(0xFF1E1E1E),
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- borderSide: BorderSide.none,
+ if (_searchController.text.isNotEmpty)
+ IconButton(
+ icon: const Icon(Icons.clear, color: Colors.grey),
+ onPressed: () {
+ _searchController.clear();
+ _hideSearchOverlay();
+ setState(() {
+ _searchResults = [];
+ _statusMessage = '';
+ });
+ },
+ ),
+ ],
+ ),
+ filled: true,
+ fillColor: const Color(0xFF1E1E1E),
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(12),
+ borderSide: BorderSide.none,
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(12),
+ borderSide: const BorderSide(color: Color(0xFF6366F1), width: 2),
+ ),
),
+ onChanged: (value) {
+ // Search as user types (with debounce)
+ if (value.length >= 3) {
+ Future.delayed(const Duration(milliseconds: 500), () {
+ if (_searchController.text == value) {
+ _searchSpotify(value);
+ }
+ });
+ } else if (value.isEmpty) {
+ setState(() {
+ _searchResults = [];
+ _statusMessage = '';
+ });
+ _hideSearchOverlay();
+ }
+ },
+ onSubmitted: _searchSpotify,
),
- onSubmitted: _searchSpotify,
),
// Atmosphere Info
@@ -323,33 +550,51 @@ class _VotingPageState extends State {
),
),
- // Search Results and Queue
+ // Queue Section
Expanded(
- child: DefaultTabController(
- length: 2,
- child: Column(
- children: [
- const TabBar(
- labelColor: Color(0xFF6366F1),
- unselectedLabelColor: Colors.grey,
- indicatorColor: Color(0xFF6366F1),
- tabs: [
- Tab(text: 'Search Results'),
- Tab(text: 'Queue'),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ const Text(
+ 'Music Queue',
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
+ ),
+ ),
+ Container(
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
+ decoration: BoxDecoration(
+ color: const Color(0xFF6366F1).withOpacity(0.2),
+ borderRadius: BorderRadius.circular(20),
+ border: Border.all(
+ color: const Color(0xFF6366F1),
+ width: 1,
+ ),
+ ),
+ child: Text(
+ '${queueService.queue.length} songs',
+ style: const TextStyle(
+ color: Color(0xFF6366F1),
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
],
),
- Expanded(
- child: TabBarView(
- children: [
- // Search Results Tab
- _buildSearchResults(),
- // Queue Tab
- _buildQueueView(queueService),
- ],
- ),
- ),
- ],
- ),
+ ),
+ const SizedBox(height: 16),
+ Expanded(
+ child: _buildQueueView(queueService),
+ ),
+ ],
),
),
],
@@ -360,40 +605,6 @@ class _VotingPageState extends State {
);
}
- Widget _buildSearchResults() {
- if (_searchResults.isEmpty && !_isLoading) {
- return const Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(
- Icons.search,
- size: 80,
- color: Colors.grey,
- ),
- SizedBox(height: 20),
- Text(
- 'Search for songs to add to the queue',
- style: TextStyle(
- color: Colors.grey,
- fontSize: 16,
- ),
- ),
- ],
- ),
- );
- }
-
- return ListView.builder(
- padding: const EdgeInsets.all(20),
- itemCount: _searchResults.length,
- itemBuilder: (context, index) {
- final track = _searchResults[index];
- return _buildTrackCard(track);
- },
- );
- }
-
Widget _buildQueueView(MusicQueueService queueService) {
final queue = queueService.queue;
@@ -439,97 +650,6 @@ class _VotingPageState extends State {
);
}
- Widget _buildTrackCard(SpotifyTrack track) {
- return Card(
- color: const Color(0xFF1E1E1E),
- margin: const EdgeInsets.only(bottom: 12),
- child: Padding(
- padding: const EdgeInsets.all(12),
- child: Row(
- children: [
- // Album Art
- Container(
- width: 60,
- height: 60,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(8),
- color: const Color(0xFF2A2A2A),
- ),
- child: track.album.images.isNotEmpty
- ? ClipRRect(
- borderRadius: BorderRadius.circular(8),
- child: Image.network(
- track.album.images.first.url,
- fit: BoxFit.cover,
- errorBuilder: (context, error, stackTrace) {
- return const Icon(
- Icons.music_note,
- color: Colors.grey,
- );
- },
- ),
- )
- : const Icon(
- Icons.music_note,
- color: Colors.grey,
- ),
- ),
- const SizedBox(width: 12),
-
- // Track Info
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- track.name,
- style: const TextStyle(
- color: Colors.white,
- fontWeight: FontWeight.bold,
- fontSize: 16,
- ),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- ),
- const SizedBox(height: 4),
- Text(
- track.artists.map((a) => a.name).join(', '),
- style: const TextStyle(
- color: Colors.grey,
- fontSize: 14,
- ),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- ),
- const SizedBox(height: 4),
- Text(
- track.album.name,
- style: const TextStyle(
- color: Colors.grey,
- fontSize: 12,
- ),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- ),
- ],
- ),
- ),
-
- // Add Button
- IconButton(
- onPressed: () => _addToQueue(track),
- icon: const Icon(
- Icons.add_circle,
- color: Color(0xFF6366F1),
- size: 32,
- ),
- ),
- ],
- ),
- ),
- );
- }
-
Widget _buildQueueItemCard(QueueItem queueItem, int index, MusicQueueService queueService) {
return Card(
color: const Color(0xFF1E1E1E),
diff --git a/CHALLENGE_2/sleepysound/lib/services/network_group_service.dart b/CHALLENGE_2/sleepysound/lib/services/network_group_service.dart
index c706210..ccdca0f 100644
--- a/CHALLENGE_2/sleepysound/lib/services/network_group_service.dart
+++ b/CHALLENGE_2/sleepysound/lib/services/network_group_service.dart
@@ -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 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 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 _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 _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 refreshNetwork() async {
await _checkConnectivity();
@@ -426,6 +520,45 @@ class NetworkGroupService extends ChangeNotifier {
return 'Connected to $_currentNetworkName';
}
+ // Join another user's listening session
+ Future 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;
diff --git a/CHALLENGE_2/sleepysound/lib/services/spam_protection_service.dart b/CHALLENGE_2/sleepysound/lib/services/spam_protection_service.dart
index d6472a8..3eaeb49 100644
--- a/CHALLENGE_2/sleepysound/lib/services/spam_protection_service.dart
+++ b/CHALLENGE_2/sleepysound/lib/services/spam_protection_service.dart
@@ -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;
}
diff --git a/CHALLENGE_2/sleepysound/pubspec.lock b/CHALLENGE_2/sleepysound/pubspec.lock
index 3cce59a..952bb96 100644
--- a/CHALLENGE_2/sleepysound/pubspec.lock
+++ b/CHALLENGE_2/sleepysound/pubspec.lock
@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.7.1"
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.7"
args:
dependency: transitive
description:
@@ -177,6 +185,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
+ cli_util:
+ dependency: transitive
+ description:
+ name: cli_util
+ sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.2"
clock:
dependency: transitive
description:
@@ -294,6 +310,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_launcher_icons:
+ dependency: "direct dev"
+ description:
+ name: flutter_launcher_icons
+ sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.13.1"
flutter_lints:
dependency: "direct dev"
description:
@@ -360,6 +384,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
+ image:
+ dependency: transitive
+ description:
+ name: image
+ sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.5.4"
io:
dependency: transitive
description:
@@ -600,6 +632,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
+ posix:
+ dependency: transitive
+ description:
+ name: posix
+ sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.3"
provider:
dependency: "direct main"
description:
diff --git a/CHALLENGE_2/sleepysound/pubspec.yaml b/CHALLENGE_2/sleepysound/pubspec.yaml
index d56c97c..fef8b3e 100644
--- a/CHALLENGE_2/sleepysound/pubspec.yaml
+++ b/CHALLENGE_2/sleepysound/pubspec.yaml
@@ -77,6 +77,9 @@ dev_dependencies:
# JSON serialization
json_serializable: ^6.7.1
build_runner: ^2.4.7
+
+ # App icon generator
+ flutter_launcher_icons: ^0.13.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
@@ -92,6 +95,7 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- assets/audio/
+ - assets/icons/
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
@@ -120,3 +124,13 @@ flutter:
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
+
+flutter_launcher_icons:
+ android: true
+ ios: true
+ web:
+ generate: true
+ image_path: "assets/icons/app_icon.png"
+ adaptive_icon_background: "#121212"
+ adaptive_icon_foreground: "assets/icons/app_icon_foreground.png"
+ remove_alpha_ios: true
diff --git a/CHALLENGE_2/sleepysound/web/favicon.png b/CHALLENGE_2/sleepysound/web/favicon.png
index 8aaa46a..4cf62ff 100644
Binary files a/CHALLENGE_2/sleepysound/web/favicon.png and b/CHALLENGE_2/sleepysound/web/favicon.png differ
diff --git a/CHALLENGE_2/sleepysound/web/icons/Icon-192.png b/CHALLENGE_2/sleepysound/web/icons/Icon-192.png
index b749bfe..f087383 100644
Binary files a/CHALLENGE_2/sleepysound/web/icons/Icon-192.png and b/CHALLENGE_2/sleepysound/web/icons/Icon-192.png differ
diff --git a/CHALLENGE_2/sleepysound/web/icons/Icon-512.png b/CHALLENGE_2/sleepysound/web/icons/Icon-512.png
index 88cfd48..03214a1 100644
Binary files a/CHALLENGE_2/sleepysound/web/icons/Icon-512.png and b/CHALLENGE_2/sleepysound/web/icons/Icon-512.png differ
diff --git a/CHALLENGE_2/sleepysound/web/icons/Icon-maskable-192.png b/CHALLENGE_2/sleepysound/web/icons/Icon-maskable-192.png
index eb9b4d7..f087383 100644
Binary files a/CHALLENGE_2/sleepysound/web/icons/Icon-maskable-192.png and b/CHALLENGE_2/sleepysound/web/icons/Icon-maskable-192.png differ
diff --git a/CHALLENGE_2/sleepysound/web/icons/Icon-maskable-512.png b/CHALLENGE_2/sleepysound/web/icons/Icon-maskable-512.png
index d69c566..03214a1 100644
Binary files a/CHALLENGE_2/sleepysound/web/icons/Icon-maskable-512.png and b/CHALLENGE_2/sleepysound/web/icons/Icon-maskable-512.png differ
diff --git a/CHALLENGE_2/sleepysound/web/manifest.json b/CHALLENGE_2/sleepysound/web/manifest.json
index 59317b1..e9218d8 100644
--- a/CHALLENGE_2/sleepysound/web/manifest.json
+++ b/CHALLENGE_2/sleepysound/web/manifest.json
@@ -32,4 +32,4 @@
"purpose": "maskable"
}
]
-}
+}
\ No newline at end of file