@@ -111,6 +111,10 @@ class _DrinkDetailScreenState extends State<DrinkDetailScreen> {
111111 SliverToBoxAdapter (
112112 child: _buildBrewerySection (context, drink),
113113 ),
114+ // Tasting history section
115+ SliverToBoxAdapter (
116+ child: _buildTastingHistory (context, drink, provider),
117+ ),
114118 // Similar drinks
115119 ..._buildSimilarDrinksSlivers (context, drink, provider),
116120 const SliverPadding (padding: EdgeInsets .only (bottom: 16 )),
@@ -328,6 +332,98 @@ class _DrinkDetailScreenState extends State<DrinkDetailScreen> {
328332 );
329333 }
330334
335+ Widget _buildTastingHistory (BuildContext context, Drink drink, BeerProvider provider) {
336+ return FutureBuilder <List <DateTime >>(
337+ future: provider.getTastingTimestamps (drink),
338+ builder: (context, snapshot) {
339+ if (! snapshot.hasData || snapshot.data! .isEmpty) {
340+ return const SizedBox .shrink ();
341+ }
342+
343+ final timestamps = snapshot.data! ;
344+
345+ return Column (
346+ crossAxisAlignment: CrossAxisAlignment .start,
347+ children: [
348+ const SectionHeader (title: 'Tasting History' ),
349+ Padding (
350+ padding: const EdgeInsets .symmetric (horizontal: 16.0 ),
351+ child: Card (
352+ child: Column (
353+ children: [
354+ for (int i = 0 ; i < timestamps.length; i++ )
355+ Semantics (
356+ label: 'Tasting ${i + 1 } on ${_formatTryDate (timestamps [i ])}' ,
357+ button: true ,
358+ child: ListTile (
359+ leading: const Icon (Icons .check_circle, color: Colors .green),
360+ title: Text (_formatTryDate (timestamps[i])),
361+ subtitle: i == 0 ? const Text ('First tasting' ) : null ,
362+ trailing: Semantics (
363+ label: 'Delete tasting from ${_formatTryDate (timestamps [i ])}' ,
364+ hint: 'Double tap to delete this tasting timestamp' ,
365+ button: true ,
366+ child: IconButton (
367+ icon: const Icon (Icons .delete_outline),
368+ tooltip: 'Delete tasting' ,
369+ onPressed: () => _confirmDeleteTry (context, drink, timestamps[i], provider),
370+ ),
371+ ),
372+ ),
373+ ),
374+ ],
375+ ),
376+ ),
377+ ),
378+ ],
379+ );
380+ },
381+ );
382+ }
383+
384+ void _confirmDeleteTry (BuildContext context, Drink drink, DateTime tryDate, BeerProvider provider) {
385+ showDialog (
386+ context: context,
387+ builder: (context) => AlertDialog (
388+ title: const Text ('Delete tasting?' ),
389+ content: Text ('Remove tasting from ${_formatTryDate (tryDate )}?' ),
390+ actions: [
391+ TextButton (
392+ onPressed: () => Navigator .pop (context),
393+ child: const Text ('Cancel' ),
394+ ),
395+ TextButton (
396+ onPressed: () async {
397+ Navigator .pop (context);
398+ await provider.deleteTry (drink, tryDate);
399+ if (context.mounted) {
400+ ScaffoldMessenger .of (context).showSnackBar (
401+ const SnackBar (
402+ content: Text ('Tasting deleted' ),
403+ duration: Duration (seconds: 1 ),
404+ ),
405+ );
406+ }
407+ },
408+ child: const Text ('Delete' ),
409+ ),
410+ ],
411+ ),
412+ );
413+ }
414+
415+ String _formatTryDate (DateTime date) {
416+ // Format like: "Dec 23, 2025 at 2:30 PM"
417+ final months = ['Jan' , 'Feb' , 'Mar' , 'Apr' , 'May' , 'Jun' , 'Jul' , 'Aug' , 'Sep' , 'Oct' , 'Nov' , 'Dec' ];
418+ final month = months[date.month - 1 ];
419+ final day = date.day;
420+ final year = date.year;
421+ final hour = date.hour > 12 ? date.hour - 12 : (date.hour == 0 ? 12 : date.hour);
422+ final minute = date.minute.toString ().padLeft (2 , '0' );
423+ final period = date.hour >= 12 ? 'PM' : 'AM' ;
424+ return '$month $day , $year at $hour :$minute $period ' ;
425+ }
426+
331427 List <Widget > _buildSimilarDrinksSlivers (BuildContext context, Drink drink, BeerProvider provider) {
332428 final similarDrinksWithReasons = _getSimilarDrinksWithReasons (drink, provider.allDrinks);
333429
@@ -365,15 +461,62 @@ class _DrinkDetailScreenState extends State<DrinkDetailScreen> {
365461 Widget _buildBottomActionBar (BuildContext context, Drink drink, BeerProvider provider) {
366462 return BottomActionBar (
367463 actions: [
368- // Tasted checkbox
369- ActionButton (
370- icon: drink.isTasted ? Icons .check_box : Icons .check_box_outline_blank,
371- label: 'Tasted' ,
372- isActive: drink.isTasted,
373- onPressed: () => provider.toggleTasted (drink),
374- semanticLabel: drink.isTasted
375- ? 'Mark ${drink .name } as not tasted'
376- : 'Mark ${drink .name } as tasted' ,
464+ // Mark as Tasted button
465+ FutureBuilder <(String ?, int )>(
466+ future: Future .wait ([
467+ provider.getFavoriteStatus (drink),
468+ provider.getTryCount (drink),
469+ ]).then ((results) => (results[0 ] as String ? , results[1 ] as int )),
470+ builder: (context, snapshot) {
471+ if (! snapshot.hasData) {
472+ return ActionButton (
473+ icon: Icons .check_circle_outline,
474+ label: 'Mark as Tasted' ,
475+ onPressed: () => provider.markAsTasted (drink),
476+ semanticLabel: 'Mark ${drink .name } as tasted' ,
477+ );
478+ }
479+
480+ final (status, tryCount) = snapshot.data! ;
481+
482+ // Show different button based on status
483+ if (status == 'tasted' && tryCount > 0 ) {
484+ return ActionButton (
485+ icon: Icons .check_circle,
486+ label: 'Tasted ${tryCount }x' ,
487+ isActive: true ,
488+ onPressed: () async {
489+ await provider.markAsTasted (drink);
490+ if (context.mounted) {
491+ ScaffoldMessenger .of (context).showSnackBar (
492+ const SnackBar (
493+ content: Text ('Tasted again!' ),
494+ duration: Duration (seconds: 1 ),
495+ ),
496+ );
497+ }
498+ },
499+ semanticLabel: 'Mark ${drink .name } as tasted again. Currently tasted $tryCount times' ,
500+ );
501+ } else {
502+ return ActionButton (
503+ icon: Icons .check_circle_outline,
504+ label: 'Mark as Tasted' ,
505+ onPressed: () async {
506+ await provider.markAsTasted (drink);
507+ if (context.mounted) {
508+ ScaffoldMessenger .of (context).showSnackBar (
509+ const SnackBar (
510+ content: Text ('Marked as tasted!' ),
511+ duration: Duration (seconds: 1 ),
512+ ),
513+ );
514+ }
515+ },
516+ semanticLabel: 'Mark ${drink .name } as tasted' ,
517+ );
518+ }
519+ },
377520 ),
378521 // Rating
379522 Semantics (
0 commit comments