diff --git a/lib/main.dart b/lib/main.dart index 7607a58..ce1703c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -82,12 +82,13 @@ class MyApp extends StatelessWidget { } } +const spotifyWidgetColor = Color(0xFF121212); final spotifyThemeData = ThemeData( primarySwatch: Colors.green, brightness: Brightness.dark, - scaffoldBackgroundColor: const Color(0xFF121212), + scaffoldBackgroundColor: spotifyWidgetColor, appBarTheme: const AppBarTheme( - backgroundColor: Color(0xFF121212), + backgroundColor: spotifyWidgetColor, elevation: 0, iconTheme: IconThemeData(color: Colors.white), titleTextStyle: TextStyle( @@ -101,6 +102,18 @@ final spotifyThemeData = ThemeData( borderRadius: BorderRadius.circular(25), ), ), + expansionTileTheme: ExpansionTileThemeData( + backgroundColor: spotifyWidgetColor, + collapsedBackgroundColor: spotifyWidgetColor, + textColor: Colors.white, + collapsedTextColor: Colors.white, + iconColor: Colors.white, + collapsedIconColor: Colors.white, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(4), + // // side: BorderSide(color: Colors.white24), + // ), + ), textTheme: TextTheme( titleLarge: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), @@ -109,7 +122,7 @@ final spotifyThemeData = ThemeData( labelMedium: TextStyle(color: Colors.grey[400]), ), inputDecorationTheme: InputDecorationTheme( - fillColor: const Color(0xFF212121), + fillColor: spotifyWidgetColor, filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(4), @@ -138,18 +151,10 @@ final spotifyThemeData = ThemeData( ), ), listTileTheme: const ListTileThemeData( - tileColor: Color(0xFF212121), + tileColor: Color(0xFF121212), textColor: Colors.white, iconColor: Colors.white54, ), - expansionTileTheme: const ExpansionTileThemeData( - backgroundColor: Color(0xFF212121), - collapsedBackgroundColor: Color(0xFF212121), - textColor: Colors.white, - collapsedTextColor: Colors.white, - iconColor: Colors.white, - collapsedIconColor: Colors.white, - ), switchTheme: SwitchThemeData( thumbColor: MaterialStateProperty.all(Colors.white), trackColor: MaterialStateProperty.all(Colors.green), diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index b2c431b..9aae0b9 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:spotify/spotify.dart'; import 'package:spotkin_flutter/app_core.dart'; import '../widgets/info_button.dart'; +import '../widgets/playlist_image_icon.dart'; import '../widgets/spotify_button.dart'; class HomeScreen extends StatefulWidget { @@ -28,6 +29,7 @@ class _HomeScreenState extends State { final SpotifyService spotifyService = getIt(); bool _isExpanded = false; Key _expansionTileKey = UniqueKey(); + final widgetPadding = 3.0; // bool _isResettingTargetPlaylist = false; @@ -82,7 +84,7 @@ class _HomeScreenState extends State { print(results[0]['result'].runtimeType); final result = results[0]['result'] as String; print('Error processing jobs: $result'); - if (result.startsWith("Status 401")) { + if (result.startsWith("Status widgetPadding01")) { print('Token expired, authenticate again...'); spotifyService.initiateSpotifyLogin(); return; @@ -134,7 +136,11 @@ class _HomeScreenState extends State { } Widget _buildRecipeCard(Job job, int index) { - return Card( + return Container( + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: BorderRadius.circular(12), + ), child: Padding( padding: const EdgeInsets.all(8), child: Column( @@ -165,32 +171,64 @@ class _HomeScreenState extends State { appBar: AppBar( title: const Text('Spotkin'), automaticallyImplyLeading: false, - actions: const [InfoButton()], + actions: [ + IconButton( + icon: const Icon(Icons.settings), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SettingsScreen( + jobs: jobs, + updateJob: updateJob, + ), + ), + ); + }, + ), + ], ), body: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.all(16), + padding: EdgeInsets.symmetric(vertical: widgetPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ - ExpansionTile( - key: _expansionTileKey, - title: PlaylistTitle(context, targetPlaylist), - leading: PlaylistImageIcon(playlist: targetPlaylist), - subtitle: playlistSubtitle(targetPlaylist, context), - initiallyExpanded: _isExpanded, - onExpansionChanged: (expanded) { - setState(() { - _isExpanded = expanded; - }); - }, - children: [ - buildTargetPlaylistSelectionOptions(), - ], + Material( + // color: Theme.of(context).cardColor, + elevation: 1, + borderRadius: BorderRadius.circular(12.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(12.0), + child: ExpansionTile( + key: _expansionTileKey, + title: Column(children: [ + PlaylistImageIcon( + playlist: targetPlaylist, + size: 120, + ), + const SizedBox(height: 10), + PlaylistTitle(context, targetPlaylist), + const SizedBox(height: 5), + playlistSubtitle(targetPlaylist, context) + ]), + // leading: , + // subtitle: , + initiallyExpanded: _isExpanded, + onExpansionChanged: (expanded) { + setState(() { + _isExpanded = expanded; + }); + }, + children: [ + buildTargetPlaylistSelectionOptions(), + ], + ), + ), ), - const SizedBox(height: 20), + SizedBox(height: widgetPadding), ...jobs.asMap().entries.map((entry) { return _buildRecipeCard(entry.value, entry.key); }), diff --git a/lib/widgets/bottom_sheets/search_bottom_sheet.dart b/lib/widgets/bottom_sheets/search_bottom_sheet.dart index d88727e..171a02f 100644 --- a/lib/widgets/bottom_sheets/search_bottom_sheet.dart +++ b/lib/widgets/bottom_sheets/search_bottom_sheet.dart @@ -6,12 +6,14 @@ class SearchBottomSheet extends StatefulWidget { final Function(dynamic) onItemSelected; final List? searchTypes; final bool userPlaylistsOnly; + final String? title; const SearchBottomSheet({ Key? key, required this.onItemSelected, this.searchTypes, this.userPlaylistsOnly = false, + this.title, }) : assert( !(userPlaylistsOnly && (searchTypes != null)), 'searchTypes should not be provided when userPlaylistsOnly is true', @@ -170,10 +172,17 @@ class _SearchBottomSheetState extends State { ); } + String title = 'Search'; + @override Widget build(BuildContext context) { + if (widget.title != null) { + title = widget.title!; + } else if (widget.userPlaylistsOnly) { + title = 'Your Playlists'; + } return CustomBottomSheet( - title: Text(widget.userPlaylistsOnly ? 'Your Playlists' : 'Search'), + title: Text(title), content: [ if (!widget.userPlaylistsOnly) Padding( diff --git a/lib/widgets/playlist_image_icon.dart b/lib/widgets/playlist_image_icon.dart new file mode 100644 index 0000000..b5b7f5e --- /dev/null +++ b/lib/widgets/playlist_image_icon.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotkin_flutter/app_core.dart'; + +class PlaylistImageIcon extends StatelessWidget { + const PlaylistImageIcon({super.key, required this.playlist, this.size = 56}); + + final PlaylistSimple playlist; + final double? size; + + @override + Widget build(BuildContext context) { + return Container( + width: size, + height: size, + decoration: BoxDecoration( + color: Colors.grey[800], + borderRadius: BorderRadius.circular(4), + ), + child: Utils.getPlaylistImageOrIcon(playlist), + ); + } +} diff --git a/lib/widgets/playlist_widgets.dart b/lib/widgets/playlist_widgets.dart index ad20fb8..e513b9a 100644 --- a/lib/widgets/playlist_widgets.dart +++ b/lib/widgets/playlist_widgets.dart @@ -17,7 +17,8 @@ class PlaylistNameField extends StatelessWidget { @override Widget build(BuildContext context) { return playlistName != null - ? Text(playlistName!, style: TextStyle(fontWeight: FontWeight.bold)) + ? Text(playlistName!, + style: const TextStyle(fontWeight: FontWeight.bold)) : TextFormField( controller: playlistController, decoration: const InputDecoration( @@ -34,6 +35,8 @@ Widget playlistSubtitle(PlaylistSimple playlist, BuildContext context) { ? Text( 'Playlist • ${playlist.owner!.displayName}', style: Theme.of(context).textTheme.labelMedium, + overflow: TextOverflow.ellipsis, + maxLines: 1, ) : const SizedBox(); } @@ -43,5 +46,6 @@ Text PlaylistTitle(BuildContext context, PlaylistSimple playlist) { playlist.name ?? 'Unknown Playlist', style: Theme.of(context).textTheme.titleMedium, overflow: TextOverflow.ellipsis, + maxLines: 1, ); } diff --git a/lib/widgets/recipe_widget.dart b/lib/widgets/recipe_widget.dart index 31bbf00..399ff9a 100644 --- a/lib/widgets/recipe_widget.dart +++ b/lib/widgets/recipe_widget.dart @@ -83,9 +83,9 @@ class _RecipeWidgetState extends State { }); } - Widget buildQuantityDropdown(IngredientRow row) { + Widget buildQuantityDropdown(IngredientRow row, int index) { return SizedBox( - width: 80, // Adjust the width as needed + width: 80, child: DropdownButtonFormField( value: int.tryParse(row.quantityController.text) ?? 5, items: List.generate(21, (index) { @@ -95,7 +95,12 @@ class _RecipeWidgetState extends State { ); }), onChanged: (value) { - row.quantityController.text = value.toString(); + if (value != null) { + setState(() { + row.quantityController.text = value.toString(); + _updateJobInStorage(index, value); + }); + } }, validator: (value) { if (value == null) { @@ -111,47 +116,45 @@ class _RecipeWidgetState extends State { ); } + void _updateJobInStorage(int index, int newQuantity) { + final job = storageService.getJobs().first; + final updatedRecipe = List.from(job.recipe); + updatedRecipe[index] = updatedRecipe[index].copyWith(quantity: newQuantity); + + final updatedJob = job.copyWith(recipe: updatedRecipe); + storageService.updateJob(updatedJob); + } + @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ListTile( - leading: const Icon( - Icons.add, - ), - title: const Text( - 'Add', - ), - trailing: IconButton( - icon: const Icon(Icons.settings), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SettingsScreen( - jobs: widget.jobs, - updateJob: widget.updateJob, - ), - ), - ); - }, - ), - onTap: () async { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (BuildContext context) { - return SearchBottomSheet( - onItemSelected: (dynamic item) { - if (item is PlaylistSimple) { - _addNewRow(item, storageService.getJobs().first); - } - }, - searchTypes: const [SearchType.playlist], - ); - }); - }, + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + icon: const Icon( + Icons.add, + ), + onPressed: () async { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (BuildContext context) { + return SearchBottomSheet( + onItemSelected: (dynamic item) { + if (item is PlaylistSimple) { + _addNewRow(item, storageService.getJobs().first); + } + }, + searchTypes: const [SearchType.playlist], + title: 'Add a playlist', + ); + }); + }, + ), + ], ), if (_ingredientRows.isEmpty) Padding( @@ -164,49 +167,17 @@ class _RecipeWidgetState extends State { ) else ..._ingredientRows.asMap().entries.map((entry) { + int index = entry.key; IngredientRow row = entry.value; final playlist = row.playlist; if (playlist == null) { return const SizedBox.shrink(); } return SpotifyStylePlaylistTile( - playlist: row.playlist!, - trailingButton: buildQuantityDropdown(row), - onTileTapped: () async { - // update the job.recipe - // String playlistName = playlist.name ?? 'Unknown Playlist'; - - // // Update the row with the fetched playlist name - // setState(() { - // // lastRow.playlistName = playlistName; - // // lastRow.playlist = playlist; - // // Add the new ingredient to the list - // widget.onIngredientsChanged( - // [...widget.initialIngredients, newIngredient]); - // }); - } - - // setState(() { - // _hasChanges = false; - // _isSubmitting = false; - // // Add a new empty row for the next ingredient - // _addNewRow(); - // }); - - // }, - ); + playlist: row.playlist!, + trailingButton: buildQuantityDropdown(row, index), + ); }), - // const SizedBox(height: 10), - // if (_hasChanges && - // _ingredientRows.isNotEmpty && - // // _ingredientRows.last.playlistController.text.isNotEmpty && - // _ingredientRows.last.quantityController.text.isNotEmpty) - // // ElevatedButton( - // // onPressed: _isSubmitting ? null : _submitForm, - // // child: _isSubmitting - // // ? const CircularProgressIndicator() - // // : const Text('Submit'), - // // ), ], ); } diff --git a/lib/widgets/spotify_style_playlist_tile.dart b/lib/widgets/spotify_style_playlist_tile.dart index 15a65e0..fa58fc4 100644 --- a/lib/widgets/spotify_style_playlist_tile.dart +++ b/lib/widgets/spotify_style_playlist_tile.dart @@ -24,7 +24,7 @@ class SpotifyStylePlaylistTile extends StatelessWidget { } }, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0), child: Row( children: [ PlaylistImageIcon(playlist: playlist), @@ -48,25 +48,3 @@ 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 571f448..816b8ae 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -1,6 +1,8 @@ export 'recipe_widget.dart'; export 'bottom_sheets/bottom_sheets.dart'; +export 'info_button.dart'; export 'playlist_widgets.dart'; +export 'playlist_image_icon.dart'; export 'target_playlist_selection_options.dart'; export 'settings_card.dart'; export 'spotify_style_playlist_tile.dart';