diff --git a/lib/Models/Track.dart b/lib/Models/Track.dart index 41a268b..8ee98ec 100644 --- a/lib/Models/Track.dart +++ b/lib/Models/Track.dart @@ -1,8 +1,9 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; +import 'package:flutter/animation.dart'; import 'package:flutter/foundation.dart'; -import 'package:palette_generator/palette_generator.dart'; +// import 'package:palette_generator/palette_generator.dart'; import 'package:vibe_music/Models/Album.dart'; import 'package:vibe_music/Models/Artist.dart'; @@ -14,7 +15,7 @@ class Track { List thumbnails; List artists; Album? albums; - PaletteGenerator? colorPalette; + ColorPalette? colorPalette; Track({ required this.title, required this.videoId, @@ -30,7 +31,7 @@ class Track { List? thumbnails, List? artists, Album? albums, - PaletteGenerator? colorPalette, + ColorPalette? colorPalette, }) { return Track( title: title ?? this.title, @@ -49,7 +50,7 @@ class Track { 'thumbnails': thumbnails.map((x) => x.toMap()).toList(), 'artists': artists.map((x) => x.toMap()).toList(), 'albums': albums?.toMap(), - 'colorPalette': colorPalette, + 'colorPalette': colorPalette?.toMap(), }; } @@ -70,7 +71,7 @@ class Track { albums: map['albums'] != null ? Album.fromMap(map['albums'] as Map) : null, - colorPalette: map['colorPalette'], + colorPalette: ColorPalette.fromMap(map['colorPalette'] ?? {}), ); } @@ -106,3 +107,60 @@ class Track { colorPalette.hashCode; } } + +class ColorPalette { + Color? darkMutedColor; + Color? lightMutedColor; + ColorPalette({ + this.darkMutedColor, + this.lightMutedColor, + }); + + ColorPalette copyWith({ + Color? darkMutedColor, + Color? lightMutedColor, + }) { + return ColorPalette( + darkMutedColor: darkMutedColor ?? this.darkMutedColor, + lightMutedColor: lightMutedColor ?? this.lightMutedColor, + ); + } + + Map toMap() { + return { + 'darkMutedColor': darkMutedColor?.value, + 'lightMutedColor': lightMutedColor?.value, + }; + } + + factory ColorPalette.fromMap(Map map) { + return ColorPalette( + darkMutedColor: map['darkMutedColor'] != null + ? Color(map['darkMutedColor'] as int) + : null, + lightMutedColor: map['lightMutedColor'] != null + ? Color(map['lightMutedColor'] as int) + : null, + ); + } + + String toJson() => json.encode(toMap()); + + factory ColorPalette.fromJson(String source) => + ColorPalette.fromMap(json.decode(source) as Map); + + @override + String toString() => + 'ColorPalette(darkMutedColor: $darkMutedColor, lightMutedColor: $lightMutedColor)'; + + @override + bool operator ==(covariant ColorPalette other) { + if (identical(this, other)) return true; + + return other.darkMutedColor == darkMutedColor && + other.lightMutedColor == lightMutedColor; + } + + @override + int get hashCode => darkMutedColor.hashCode ^ lightMutedColor.hashCode; +} diff --git a/lib/main.dart b/lib/main.dart index 8486131..60a46d3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,8 @@ import 'package:country_picker/country_picker.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_animated_theme/animated_theme_app.dart'; -import 'package:flutter_animated_theme/animation_type.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:hive_flutter/hive_flutter.dart'; import 'package:just_audio_background/just_audio_background.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -25,7 +24,9 @@ void main() async { androidNotificationChannelName: 'Audio playback', androidNotificationOngoing: false, ); - HomeApi.setCountry(); + await Hive.initFlutter(); + Hive.openBox('myfavourites'); + await HomeApi.setCountry(); runApp(MultiProvider(providers: [ ChangeNotifierProvider(create: (_) => MusicPlayer()), ChangeNotifierProvider(create: (_) => HomeScreenProvider()), @@ -43,8 +44,7 @@ class MyApp extends StatelessWidget { return DynamicColorBuilder( builder: (lightDynamic, darkDynamic) { - return AnimatedThemeApp( - animationType: AnimationType.CIRCULAR_ANIMATED_THEME, + return MaterialApp( debugShowCheckedModeBanner: false, title: 'Vibe Music', localizationsDelegates: const [ @@ -60,7 +60,7 @@ class MyApp extends StatelessWidget { colorScheme: context.watch().dynamicThemeMode ? ColorScheme.fromSwatch( primarySwatch: createMaterialColor( - song?.colorPalette?.lightMutedColor?.color ?? + song?.colorPalette?.lightMutedColor ?? const Color.fromARGB(255, 136, 240, 196))) : lightDynamic ?? ColorScheme.fromSwatch( @@ -72,7 +72,7 @@ class MyApp extends StatelessWidget { colorScheme: context.watch().dynamicThemeMode ? ColorScheme.fromSwatch( primarySwatch: createMaterialColor( - song?.colorPalette?.darkMutedColor?.color ?? + song?.colorPalette?.darkMutedColor ?? const Color.fromARGB(255, 80, 141, 115))) : darkDynamic ?? (ColorScheme.fromSwatch( diff --git a/lib/providers/MusicPlayer.dart b/lib/providers/MusicPlayer.dart index a6e5b1e..d5df1b6 100644 --- a/lib/providers/MusicPlayer.dart +++ b/lib/providers/MusicPlayer.dart @@ -71,7 +71,9 @@ class MusicPlayer extends ChangeNotifier { addToQUeue(Track newSong) async { PaletteGenerator? color = await generateColor(newSong.thumbnails.last.url); - newSong.colorPalette = color; + newSong.colorPalette = ColorPalette( + darkMutedColor: color?.darkMutedColor?.color, + lightMutedColor: color?.lightMutedColor?.color); _songs.add(newSong); _initialised = true; notifyListeners(); @@ -101,7 +103,9 @@ class MusicPlayer extends ChangeNotifier { try { PaletteGenerator? color = await generateColor(newSong.thumbnails.last.url); - newSong.colorPalette = color; + newSong.colorPalette = ColorPalette( + darkMutedColor: color?.darkMutedColor?.color, + lightMutedColor: color?.lightMutedColor?.color); await setPlayer(); diff --git a/lib/providers/ThemeProvider.dart b/lib/providers/ThemeProvider.dart index 571748d..fff4906 100644 --- a/lib/providers/ThemeProvider.dart +++ b/lib/providers/ThemeProvider.dart @@ -37,14 +37,13 @@ class ThemeProvider extends ChangeNotifier { setTheme(themeMode) { _themeMode = themeMode; - + notifyListeners(); if (themeMode == 'dark') { _currentTheme = darkTheme; } else { _currentTheme = lightTheme; } _prefs.setString('themeMode', themeMode); - notifyListeners(); } } diff --git a/lib/screens/FavouriteScreen.dart b/lib/screens/FavouriteScreen.dart new file mode 100644 index 0000000..776df3e --- /dev/null +++ b/lib/screens/FavouriteScreen.dart @@ -0,0 +1,47 @@ +import 'dart:collection'; +import 'dart:convert'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:vibe_music/Models/Track.dart'; +import 'package:vibe_music/widgets/TrackTile.dart'; + +class FavouriteScreen extends StatelessWidget { + const FavouriteScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + title: const Text("My Favorites"), + centerTitle: true, + ), + body: ValueListenableBuilder( + valueListenable: Hive.box('myfavourites').listenable(), + builder: (BuildContext context, Box box, child) { + List favourites = box.values.toList(); + favourites.sort((a, b) => a['timeStamp'].compareTo(b['timeStamp'])); + if (favourites.isEmpty) { + return Center( + child: Text( + "Nothing Here", + style: Theme.of(context).primaryTextTheme.bodyLarge, + ), + ); + } + return ListView( + children: favourites.map((track) { + Map newMap = jsonDecode(jsonEncode(track)); + return TrackTile(track: newMap); + }).toList(), + ); + }, + ), + ); + } +} diff --git a/lib/screens/HomeScreen.dart b/lib/screens/HomeScreen.dart index a5e1629..c2fbe25 100644 --- a/lib/screens/HomeScreen.dart +++ b/lib/screens/HomeScreen.dart @@ -107,7 +107,7 @@ class _HomeScreenState extends State .sublist(0, trending.length > 10 ? 10 : trending.length) .map((s) { - Track song = Track.fromJson(jsonEncode(s)); + Track song = Track.fromMap(s); return Builder( builder: (BuildContext context) { return ClipRRect( diff --git a/lib/screens/MainScreen.dart b/lib/screens/MainScreen.dart index fb9d064..88fe9a7 100644 --- a/lib/screens/MainScreen.dart +++ b/lib/screens/MainScreen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:miniplayer/miniplayer.dart'; import 'package:provider/provider.dart'; @@ -7,6 +8,7 @@ import 'package:vibe_music/providers/LanguageProvider.dart'; import 'package:vibe_music/providers/MusicPlayer.dart'; import 'package:vibe_music/providers/ThemeProvider.dart'; import 'package:vibe_music/screens/ArtistScreen.dart'; +import 'package:vibe_music/screens/FavouriteScreen.dart'; import 'package:vibe_music/screens/HomeScreen.dart'; import 'package:vibe_music/screens/PlayListScreen.dart'; import 'package:vibe_music/screens/PlayerScreen.dart'; @@ -75,6 +77,11 @@ class _MainScreenState extends State { navigatorKey: _searchNavigatorKey, ), ), + Directionality( + textDirection: + context.watch().textDirection, + child: const FavouriteScreen(), + ), Directionality( textDirection: context.watch().textDirection, @@ -128,6 +135,14 @@ class _MainScreenState extends State { selectedIcon: const Icon(Icons.search_rounded), label: S.of(context).Search, ), + NavigationDestination( + icon: Icon( + CupertinoIcons.heart, + color: isDarkTheme ? Colors.white : Colors.black, + ), + selectedIcon: const Icon(CupertinoIcons.heart_fill), + label: S.of(context).Settings, + ), NavigationDestination( icon: Icon( Icons.settings_outlined, diff --git a/lib/screens/PlayerScreen.dart b/lib/screens/PlayerScreen.dart index a4622b8..8e8fb51 100644 --- a/lib/screens/PlayerScreen.dart +++ b/lib/screens/PlayerScreen.dart @@ -1,6 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:hive_flutter/hive_flutter.dart'; import 'package:just_audio/just_audio.dart'; import 'package:provider/provider.dart'; @@ -78,22 +79,74 @@ class _PlayerScreenState extends State { padding: const EdgeInsets.all(16), child: Column( children: [ - Text(song.title, - style: Theme.of(context) - .primaryTextTheme - .titleMedium - ?.copyWith( - fontSize: 16, - fontWeight: FontWeight.w900, - overflow: TextOverflow.ellipsis, - )), - Text( - song.artists.first.name, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w900, - overflow: TextOverflow.ellipsis, - color: Color.fromARGB(255, 93, 92, 92)), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(song.title, + style: Theme.of(context) + .primaryTextTheme + .titleMedium + ?.copyWith( + fontSize: 16, + fontWeight: FontWeight.w900, + overflow: + TextOverflow.ellipsis, + )), + Text( + song.artists.first.name, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w900, + overflow: TextOverflow.ellipsis, + color: Color.fromARGB( + 255, 93, 92, 92)), + ), + ], + ), + ), + ValueListenableBuilder( + valueListenable: + Hive.box('myfavourites').listenable(), + builder: (context, Box box, child) { + Map? favourite = box.get(song.videoId); + return MaterialButton( + elevation: 0, + color: favourite != null + ? Theme.of(context) + .colorScheme + .primary + : Colors.transparent, + shape: const CircleBorder(), + onPressed: () { + if (favourite == null) { + int timeStamp = DateTime.now() + .millisecondsSinceEpoch; + Map mapSong = + song.toMap(); + mapSong['timeStamp'] = timeStamp; + + box.put(song.videoId, mapSong); + } else { + box.delete(song.videoId); + } + }, + child: Icon( + favourite == null + ? CupertinoIcons.heart + : CupertinoIcons.heart_fill, + color: isDarkTheme + ? Colors.white + : Colors.black, + )); + }, + ), + ], ), const SizedBox(height: 20), const MusicSlider(), @@ -176,11 +229,9 @@ class _PlayerScreenState extends State { case ProcessingState.buffering: case ProcessingState.loading: return CircularProgressIndicator( - color: song - .colorPalette - ?.darkVibrantColor - ?.color ?? - primaryColor, + color: isDarkTheme + ? Colors.white + : Colors.black, ); case ProcessingState.completed: return Icon( diff --git a/lib/screens/SettingsScreen.dart b/lib/screens/SettingsScreen.dart index ee38b5c..807e0e2 100644 --- a/lib/screens/SettingsScreen.dart +++ b/lib/screens/SettingsScreen.dart @@ -193,6 +193,70 @@ class SettingsScreen extends StatelessWidget { child: ListTile( onTap: () { showCountryPicker( + countryFilter: [ + "IN", + "ZZ", + "AR", + "AU", + "AT", + "BE", + "BO", + "BR", + "CA", + "CL", + "CO", + "CR", + "CZ", + "DK", + "DO", + "EC", + "EG", + "SV", + "EE", + "FI", + "FR", + "DE", + "GT", + "HN", + "HU", + "IS", + "ID", + "IE", + "IL", + "IT", + "JP", + "KE", + "LU", + "MX", + "NL", + "NZ", + "NI", + "NG", + "NO", + "PA", + "PY", + "PE", + "PL", + "PT", + "RO", + "RU", + "SA", + "RS", + "ZA", + "KR", + "ES", + "SE", + "CH", + "TZ", + "TR", + "UG", + "UA", + "AE", + "GB", + "US", + "UY", + "ZW" + ], context: context, onSelect: (Country value) { context diff --git a/lib/widgets/PanelHeader.dart b/lib/widgets/PanelHeader.dart index 39dbb3f..d17f2a3 100644 --- a/lib/widgets/PanelHeader.dart +++ b/lib/widgets/PanelHeader.dart @@ -136,15 +136,8 @@ class PanelHeader extends StatelessWidget { } switch (state.processingState) { case ProcessingState.buffering: - return CircularProgressIndicator( - color: song.colorPalette?.darkVibrantColor - ?.color ?? - primaryColor); case ProcessingState.loading: - return CircularProgressIndicator( - color: song.colorPalette?.darkVibrantColor - ?.color ?? - primaryColor); + return const CircularProgressIndicator(); case ProcessingState.completed: return const Icon(Icons.play_arrow); case ProcessingState.idle: diff --git a/lib/widgets/TrackTile.dart b/lib/widgets/TrackTile.dart index e9b9fa1..92ee276 100644 --- a/lib/widgets/TrackTile.dart +++ b/lib/widgets/TrackTile.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:hive_flutter/hive_flutter.dart'; import 'package:provider/provider.dart'; import 'package:vibe_music/Models/Track.dart'; import 'package:vibe_music/providers/ThemeProvider.dart'; @@ -41,6 +42,38 @@ class TrackTile extends StatelessWidget { overflow: TextOverflow.ellipsis, fontSize: 16), ), ), + ), + ValueListenableBuilder( + valueListenable: Hive.box('myfavourites').listenable(), + builder: (context, Box box, child) { + Map? favourite = box.get(song.videoId); + return Material( + child: ListTile( + onTap: () { + if (favourite == null) { + int timeStamp = + DateTime.now().millisecondsSinceEpoch; + Map mapSong = song.toMap(); + mapSong['timeStamp'] = timeStamp; + + box.put(song.videoId, mapSong); + } else { + box.delete(song.videoId); + } + Navigator.pop(context); + }, + title: Text( + "${favourite == null ? "Add to" : "Remove from"} Favourites", + style: Theme.of(context) + .primaryTextTheme + .titleMedium + ?.copyWith( + overflow: TextOverflow.ellipsis, + fontSize: 16), + ), + ), + ); + }, ) ], ); diff --git a/pubspec.lock b/pubspec.lock index a4a7690..2602e7c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -160,13 +160,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_animated_theme: - dependency: "direct main" - description: - name: flutter_animated_theme - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" flutter_blurhash: dependency: transitive description: @@ -224,6 +217,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + hive: + dependency: "direct main" + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" html: dependency: transitive description: @@ -448,13 +455,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.0.4" - rect_getter: - dependency: transitive - description: - name: rect_getter - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" rxdart: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2247351..20bf98c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,8 +27,9 @@ dependencies: font_awesome_flutter: ^10.3.0 url_launcher: ^6.1.7 expandable_page_view: ^1.0.17 - flutter_animated_theme: ^1.0.0 country_picker: ^2.0.19 + hive: ^2.2.3 + hive_flutter: ^1.1.0 dev_dependencies: flutter_test: