diff --git a/lib/main.dart b/lib/main.dart index c9689f1..e9cc676 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,7 +40,7 @@ class MyApp extends StatelessWidget { color: Colors.black, // Match your scaffold background color child: Center( child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 700), + constraints: BoxConstraints(maxWidth: 600), child: child!, ), ), @@ -93,10 +93,11 @@ final spotifyThemeData = ThemeData( titleTextStyle: TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold), ), - textTheme: const TextTheme( - headline6: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), - bodyText2: TextStyle(color: Colors.white70), - subtitle1: TextStyle(color: Colors.white54), + textTheme: TextTheme( + titleLarge: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + bodyMedium: TextStyle(color: Colors.white70), + titleMedium: TextStyle(color: Colors.white54), + labelSmall: TextStyle(color: Colors.grey), ), inputDecorationTheme: InputDecorationTheme( fillColor: const Color(0xFF212121), diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 4abd3c4..1612d34 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -26,7 +26,7 @@ class _HomeScreenState extends State { late ApiService _apiService; late StorageService _storageService; final SpotifyService spotifyService = getIt(); - bool _isResettingTargetPlaylist = false; + // bool _isResettingTargetPlaylist = false; @override void initState() { @@ -119,21 +119,14 @@ class _HomeScreenState extends State { final updateJob = jobs[0].copyWith(targetPlaylist: selectedPlaylist); _replaceJob(updateJob); } - setState(() { - _isResettingTargetPlaylist = false; - }); + // setState(() { + // _isResettingTargetPlaylist = false; + // }); }, ); } Widget _buildRecipeCard(Job job, int index) { - // if (job.recipe.isEmpty) { - // print('Job ${job.targetPlaylist.name} recipe is empty'); - // return const SizedBox(); - // } else { - // print( - // 'Job ${job.targetPlaylist.name} recipe first ingredient before passing to widget: ${job.recipe[0].playlist.id} ${job.recipe[0].quantity}'); - // } return Card( child: Padding( padding: const EdgeInsets.all(8), @@ -149,7 +142,7 @@ class _HomeScreenState extends State { updateJob(index, job); }); }, - ) + ), ], ), ), @@ -160,7 +153,6 @@ class _HomeScreenState extends State { Widget build(BuildContext context) { final job = jobs.isEmpty ? Job.empty() : jobs[0]; final targetPlaylist = job.targetPlaylist; - // final spotifyService = getIt(); return Scaffold( appBar: AppBar( title: const Text('Spotkin'), @@ -178,12 +170,6 @@ class _HomeScreenState extends State { ); }, ), - IconButton( - icon: const Icon(Icons.info), - onPressed: () { - showInfoDialog(context); - }, - ) ], ), body: SingleChildScrollView( @@ -194,36 +180,43 @@ class _HomeScreenState extends State { children: [ Column( children: [ - jobs.isEmpty || _isResettingTargetPlaylist - ? _buildPlaylistSelectionOptions() - : SpotifyStylePlaylistTile( - playlist: targetPlaylist, - trailingButton: IconButton( - icon: const Icon(Icons.edit), - onPressed: () { - setState(() { - _isResettingTargetPlaylist = true; - }); - }, - ), - onTileTapped: () { - setState(() { - _isResettingTargetPlaylist = true; - }); - }), - // ListTile( - // title: - // Text(targetPlaylist.name ?? 'Unknown Playlist'), - // leading: Utils.getPlaylistImageOrIcon(targetPlaylist), - // trailing: IconButton( - // icon: Icon(Icons.edit), - // onPressed: () { - // setState(() { - // _isResettingTargetPlaylist = true; - // }); - // }, - // ), - // ), + // jobs.isEmpty || _isResettingTargetPlaylist + // ? _buildPlaylistSelectionOptions() + // : + ExpansionTile( + title: Text(targetPlaylist.name ?? 'Unknown Playlist'), + leading: PlaylistImageIcon(playlist: targetPlaylist), + subtitle: targetPlaylist.owner != null + ? Text( + 'Playlist • ${targetPlaylist.owner!.displayName}', + style: Theme.of(context).textTheme.labelSmall, + ) + : null, + initiallyExpanded: jobs.isEmpty, + + children: [ + _buildPlaylistSelectionOptions(), + ], + + // playlist: targetPlaylist, + // trailingButton: IconButton( + // icon: const Icon(Icons.edit), + // onPressed: () { + // setState( + // () { + // _isResettingTargetPlaylist = true; + // }, + // ); + // }, + // ), + // onTileTapped: () { + // setState( + // () { + // _isResettingTargetPlaylist = true; + // }, + // ); + // }, + ), const SizedBox(height: 20), ...jobs.asMap().entries.map((entry) { return _buildRecipeCard(entry.value, entry.key); diff --git a/lib/screens/screens.dart b/lib/screens/screens.dart index 8b42127..8fa439e 100644 --- a/lib/screens/screens.dart +++ b/lib/screens/screens.dart @@ -1,5 +1,5 @@ export 'package:spotkin_flutter/screens/auth_screen.dart'; export 'package:spotkin_flutter/screens/home_screen.dart'; export 'package:spotkin_flutter/screens/list_management_screen.dart'; -export 'package:spotkin_flutter/screens/search_bottom_sheet.dart'; +export 'package:spotkin_flutter/widgets/bottom_sheets/search_bottom_sheet.dart'; export 'package:spotkin_flutter/screens/settings_screen.dart'; diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index f8f3b9b..5d9d0e4 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -13,6 +13,14 @@ class SettingsScreen extends StatelessWidget { return Scaffold( appBar: AppBar( title: const Text('Settings'), + actions: [ + IconButton( + icon: const Icon(Icons.info), + onPressed: () { + showInfoBottomSheet(context); + }, + ) + ], ), body: ListView.builder( itemCount: jobs.length, diff --git a/lib/widgets/dialogs/info_dialog_content.dart b/lib/widgets/bottom_sheets/bottom_sheet_content.dart similarity index 74% rename from lib/widgets/dialogs/info_dialog_content.dart rename to lib/widgets/bottom_sheets/bottom_sheet_content.dart index 91fedc6..48d7da2 100644 --- a/lib/widgets/dialogs/info_dialog_content.dart +++ b/lib/widgets/bottom_sheets/bottom_sheet_content.dart @@ -1,30 +1,6 @@ import 'package:flutter/material.dart'; -void showInfoDialog(BuildContext context) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('Spotkin'), - content: const Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: infoScreenContent, - ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Close'), - ), - ], - ); - }, - ); -} - -const infoScreenContent = [ +const infoSheetContent = [ Text( 'Spotkin updates one of your Spotify playlists every day with a random selection of tracks from any of your playlists.'), SizedBox(height: 10), diff --git a/lib/widgets/bottom_sheets/bottom_sheets.dart b/lib/widgets/bottom_sheets/bottom_sheets.dart new file mode 100644 index 0000000..e47f0cc --- /dev/null +++ b/lib/widgets/bottom_sheets/bottom_sheets.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'bottom_sheet_content.dart'; + +class CustomBottomSheet extends StatelessWidget { + final Widget title; + final List content; + final double initialChildSize; + final double minChildSize; + final double maxChildSize; + + const CustomBottomSheet({ + Key? key, + required this.title, + required this.content, + this.initialChildSize = 0.9, + this.minChildSize = 0.5, + this.maxChildSize = 0.9, + }) : super(key: key); + + static Future show({ + required BuildContext context, + required Widget title, + required List content, + double initialChildSize = 0.9, + double minChildSize = 0.5, + double maxChildSize = 0.9, + }) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (BuildContext context) { + return CustomBottomSheet( + title: title, + content: content, + initialChildSize: initialChildSize, + minChildSize: minChildSize, + maxChildSize: maxChildSize, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return DraggableScrollableSheet( + expand: false, + initialChildSize: initialChildSize, + minChildSize: minChildSize, + maxChildSize: maxChildSize, + builder: (_, controller) { + return Column( + children: [ + Container( + height: 5, + width: 40, + margin: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2.5), + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: DefaultTextStyle( + style: Theme.of(context).textTheme.titleLarge!, + child: title, + ), + ), + Expanded( + child: ListView.builder( + controller: controller, + itemCount: content.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: content[index], + ); + }, + ), + ), + ], + ); + }, + ); + } +} + +void showInfoBottomSheet(BuildContext context) { + CustomBottomSheet.show( + context: context, + title: Text('About Spotkin'), + content: infoSheetContent, + ); +} diff --git a/lib/screens/search_bottom_sheet.dart b/lib/widgets/bottom_sheets/search_bottom_sheet.dart similarity index 66% rename from lib/screens/search_bottom_sheet.dart rename to lib/widgets/bottom_sheets/search_bottom_sheet.dart index ab59239..ce6c7f6 100644 --- a/lib/screens/search_bottom_sheet.dart +++ b/lib/widgets/bottom_sheets/search_bottom_sheet.dart @@ -148,12 +148,12 @@ class _SearchBottomSheetState extends State { height: 50, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => - Icon(Icons.music_note, size: 50), + const Icon(Icons.music_note, size: 50), ), ); } } else { - leadingWidget = Icon(Icons.music_note, size: 50); + leadingWidget = const Icon(Icons.music_note, size: 50); } return ListTile( @@ -169,38 +169,69 @@ class _SearchBottomSheetState extends State { @override Widget build(BuildContext context) { - return Container( - height: MediaQuery.of(context).size.height * 0.8, - child: Column( - children: [ - if (!widget.userPlaylistsOnly) - Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - controller: _searchController, - decoration: InputDecoration( - hintText: 'Search...', - prefixIcon: Icon(Icons.search), - border: OutlineInputBorder(), - ), + return CustomBottomSheet( + title: Text(widget.userPlaylistsOnly ? 'Your Playlists' : 'Search'), + content: [ + if (!widget.userPlaylistsOnly) + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: _searchController, + decoration: const InputDecoration( + hintText: 'Search...', + prefixIcon: Icon(Icons.search), + border: OutlineInputBorder(), ), ), - Expanded( - child: _isLoading - ? Center(child: CircularProgressIndicator()) - : _searchResults.isEmpty - ? Center( - child: Text(widget.userPlaylistsOnly - ? 'No playlists found' - : 'No results found')) - : ListView.builder( - itemCount: _searchResults.length, - itemBuilder: (context, index) => - _buildListItem(_searchResults[index]), - ), ), - ], - ), + _isLoading + ? const Center(child: CircularProgressIndicator()) + : _searchResults.isEmpty + ? Center( + child: Text(widget.userPlaylistsOnly + ? 'No playlists found' + : 'No results found')) + : Column( + children: _searchResults + .map((item) => _buildListItem(item)) + .toList(), + ), + ], ); } + + // return Container( + // height: MediaQuery.of(context).size.height * 0.8, + // child: Column( + // children: [ + // if (!widget.userPlaylistsOnly) + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: TextField( + // controller: _searchController, + // decoration: InputDecoration( + // hintText: 'Search...', + // prefixIcon: Icon(Icons.search), + // border: OutlineInputBorder(), + // ), + // ), + // ), + // Expanded( + // child: _isLoading + // ? Center(child: CircularProgressIndicator()) + // : _searchResults.isEmpty + // ? Center( + // child: Text(widget.userPlaylistsOnly + // ? 'No playlists found' + // : 'No results found')) + // : ListView.builder( + // itemCount: _searchResults.length, + // itemBuilder: (context, index) => + // _buildListItem(_searchResults[index]), + // ), + // ), + // ], + // ), + // ); + // } } diff --git a/lib/widgets/dialogs/dialogs.dart b/lib/widgets/dialogs/dialogs.dart deleted file mode 100644 index e59c285..0000000 --- a/lib/widgets/dialogs/dialogs.dart +++ /dev/null @@ -1 +0,0 @@ -export 'info_dialog_content.dart'; diff --git a/lib/widgets/playlist_selection_options.dart b/lib/widgets/playlist_selection_options.dart index 1e337cf..142d31b 100644 --- a/lib/widgets/playlist_selection_options.dart +++ b/lib/widgets/playlist_selection_options.dart @@ -13,45 +13,49 @@ class PlaylistSelectionOptions extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - mainAxisSize: MainAxisSize.min, + // mainAxisSize: MainAxisSize.min, children: [ + const SizedBox( + height: 24, + ), Text( 'Step 1: Select which playlist you want to use for your Spotkin', - style: Theme.of(context).textTheme.headline6, + style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center, ), - SizedBox(height: 24), + const SizedBox(height: 24), Center( child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 250), + constraints: const BoxConstraints(maxWidth: 250), child: ElevatedButton( onPressed: () => _createNewPlaylist(context), - child: Text('Create a New Playlist'), style: ElevatedButton.styleFrom( - minimumSize: Size(200, 50), + minimumSize: const Size(200, 50), ), + child: const Text('Create a New Playlist'), ), ), ), - SizedBox(height: 16), + const SizedBox(height: 16), Text( 'or', - style: Theme.of(context).textTheme.subtitle1, + style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center, ), - SizedBox(height: 16), + const SizedBox(height: 16), Center( child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 250), + constraints: const BoxConstraints(maxWidth: 250), child: ElevatedButton( onPressed: () => _showPlaylistSearchBottomSheet(context), - child: Text('Select Existing Playlist'), style: ElevatedButton.styleFrom( - minimumSize: Size(200, 50), + minimumSize: const Size(200, 50), ), + child: const Text('Select Existing Playlist'), ), ), ), + const SizedBox(height: 24), ], ); } diff --git a/lib/widgets/spotify_style_playlist_tile.dart b/lib/widgets/spotify_style_playlist_tile.dart index 7751a18..9b028c6 100644 --- a/lib/widgets/spotify_style_playlist_tile.dart +++ b/lib/widgets/spotify_style_playlist_tile.dart @@ -27,28 +27,32 @@ class SpotifyStylePlaylistTile extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), child: Row( children: [ - Container( - width: 56, - height: 56, - decoration: BoxDecoration( - color: Colors.grey[800], - borderRadius: BorderRadius.circular(4), - ), - child: Utils.getPlaylistImageOrIcon(playlist), - ), + PlaylistImageIcon(playlist: playlist), const SizedBox(width: 16), // Playlist name Expanded( - child: Text( - playlist.name ?? 'Unknown Playlist', - style: const TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - overflow: TextOverflow.ellipsis, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + playlist.name ?? 'Unknown Playlist', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + ), + playlist.owner != null + ? Text( + 'Playlist • ${playlist.owner!.displayName}', + style: Theme.of(context).textTheme.labelSmall, + ) + : Container(), + ], ), ), + // Edit button trailingButton ?? const SizedBox(), ], @@ -57,3 +61,25 @@ class SpotifyStylePlaylistTile extends StatelessWidget { ); } } + +class PlaylistImageIcon extends StatelessWidget { + const PlaylistImageIcon({ + super.key, + required this.playlist, + }); + + final PlaylistSimple playlist; + + @override + Widget build(BuildContext context) { + return Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: Colors.grey[800], + borderRadius: BorderRadius.circular(4), + ), + child: Utils.getPlaylistImageOrIcon(playlist), + ); + } +} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 85749d4..ddc499b 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -1,5 +1,5 @@ export 'ingredient_form.dart'; -export 'dialogs/dialogs.dart'; +export 'bottom_sheets/bottom_sheets.dart'; export 'playlist_selection_options.dart'; export 'settings_card.dart'; export 'spotify_style_playlist_tile.dart';