import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../design/page_header.dart'; import '../../design/zen_nav_bar.dart'; import '../../design/zen_style.dart'; import '../../localization/app_localizations.dart'; import '../../settings/settings_controller.dart'; import '../exercise.dart'; import '../exercise_labels.dart'; import '../exercise_library.dart'; /// The current Mode's Exercises, by name. class LibraryView extends StatelessWidget { const LibraryView({super.key}); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final mode = context.watch().mode; final exercises = context.read().forMode(mode); final bottomInset = MediaQuery.paddingOf(context).bottom; return ListView.builder( key: PageStorageKey('library-${mode.name}'), padding: EdgeInsets.only(bottom: ZenNavBar.height + 32 + bottomInset), itemCount: exercises.length + 1, itemBuilder: (context, i) => i == 0 ? PageHeader(title: l10n.libraryTitle, subtitle: l10n.librarySubtitle(mode.name, exercises.length)) : Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 10), child: ExerciseTile(exercise: exercises[i - 1]), ), ); } } class ExerciseTile extends StatelessWidget { const ExerciseTile({super.key, required this.exercise, this.onTap}); final Exercise exercise; final VoidCallback? onTap; @override Widget build(BuildContext context) { final s = ZenStyle.of(context); final text = Theme.of(context).textTheme; final muscles = exercise.primaryMuscles.map(sentenceCase).join(', '); return Card( child: InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.all(10), child: Row( children: [ ExerciseThumbnail(exercise: exercise, size: 64), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(exercise.name, style: text.titleMedium, maxLines: 2, overflow: TextOverflow.ellipsis), const SizedBox(height: 4), Text( '$muscles ยท ${sentenceCase(exercise.level)}', style: text.bodySmall?.copyWith(color: s.inkMuted), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ), ), ); } } /// The start image in a soft well, or an icon for the few Exercises without images. class ExerciseThumbnail extends StatelessWidget { const ExerciseThumbnail({super.key, required this.exercise, required this.size}); final Exercise exercise; final double size; @override Widget build(BuildContext context) { final s = ZenStyle.of(context); final ratio = MediaQuery.devicePixelRatioOf(context); return ClipRRect( borderRadius: BorderRadius.circular((s.radius * 0.6).clamp(0, size / 2)), child: Container( width: size, height: size, color: s.surfaceSoft, child: exercise.images.isEmpty ? Icon(Icons.image_not_supported_outlined, color: s.inkMuted) : Image.asset( exercise.images.first, fit: BoxFit.cover, cacheWidth: (size * ratio).round(), errorBuilder: (_, _, _) => Icon(Icons.image_not_supported_outlined, color: s.inkMuted), ), ), ); } }