Files
bookshelf/books_flutter/lib/screens/library_screen.dart
Yuriy Panov 5c7b65a0d3 Implement light minimalistic tech redesign with Material 3
Complete transformation from dark to light theme with a professional,
tech-forward design system featuring:

- Material 3 theming with cyan-based color palette (#0891B2 primary)
- Inter font family integration via Google Fonts
- Comprehensive theme system (colors, spacing, typography, shadows)
- Responsive component redesign across all screens
- Enhanced UX with hover animations, Hero transitions, and shimmer loading
- Accessibility features (reduced motion support, high contrast)
- Clean architecture with zero hardcoded values

Theme System:
- Created app_colors.dart with semantic color constants
- Created app_spacing.dart with 8px base spacing scale
- Created app_theme.dart with complete Material 3 configuration
- Added shimmer_loading.dart for image loading states

UI Components Updated:
- Book cards with hover effects and Hero animations
- Bottom navigation with refined styling
- All screens migrated to theme-based colors and typography
- Forms and inputs using consistent design system

Documentation:
- Added REDESIGN_SUMMARY.md with complete implementation overview
- Added IMPLEMENTATION_CHECKLIST.md with detailed task completion status

All components now use centralized theme with no hardcoded values,
ensuring consistency and easy future customization.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-07 13:26:06 +06:00

201 lines
7.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/book_bloc.dart';
import '../bloc/navigation_bloc.dart';
import '../models/models.dart';
import '../widgets/book_card.dart';
import '../theme/app_spacing.dart';
class LibraryScreen extends StatefulWidget {
const LibraryScreen({super.key});
@override
State<LibraryScreen> createState() => _LibraryScreenState();
}
class _LibraryScreenState extends State<LibraryScreen> {
String _search = '';
int _tabIndex = 0;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return BlocBuilder<BookBloc, BookState>(
builder: (context, state) {
final filtered = state.books.where((b) {
final q = _search.toLowerCase();
return b.title.toLowerCase().contains(q) ||
b.author.toLowerCase().contains(q);
}).toList();
return SafeArea(
child: Column(
children: [
// Header
Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.lg,
AppSpacing.md,
AppSpacing.lg,
0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Книжная полка', style: textTheme.displayMedium),
IconButton(
icon: const Icon(Icons.notifications_outlined),
onPressed: () {},
),
],
),
),
// Search
Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.lg,
AppSpacing.md,
AppSpacing.lg,
0,
),
child: TextField(
onChanged: (v) => setState(() => _search = v),
decoration: InputDecoration(
hintText: 'Поиск книг...',
prefixIcon: Icon(Icons.search, color: colorScheme.primary),
),
),
),
// Tabs
Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.lg,
AppSpacing.md,
AppSpacing.lg,
0,
),
child: Row(
children: [
_tab('Все книги', 0),
const SizedBox(width: AppSpacing.sm),
_tab('Категории', 1),
],
),
),
const SizedBox(height: AppSpacing.md),
// Grid
Expanded(
child: _tabIndex == 0
? GridView.builder(
padding: const EdgeInsets.fromLTRB(
AppSpacing.lg,
0,
AppSpacing.lg,
100,
),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.55,
crossAxisSpacing: AppSpacing.md,
mainAxisSpacing: AppSpacing.md,
),
itemCount: filtered.length,
itemBuilder: (context, i) => BookCard(
book: filtered[i],
onTap: () {
context.read<NavigationBloc>().add(
NavigateTo(
AppScreen.details,
selectedBook: filtered[i],
),
);
},
),
)
: ListView(
padding: const EdgeInsets.fromLTRB(
AppSpacing.lg,
0,
AppSpacing.lg,
100,
),
children: [
for (final genre
in filtered.map((b) => b.genre).toSet())
Container(
margin: const EdgeInsets.only(
bottom: AppSpacing.sm,
),
decoration: BoxDecoration(
color: colorScheme.surface,
border: Border.all(color: colorScheme.outline),
borderRadius: BorderRadius.circular(
AppSpacing.radiusMedium,
),
),
child: ListTile(
title: Text(
genre,
style: textTheme.titleMedium,
),
trailing: Text(
'${filtered.where((b) => b.genre == genre).length}',
style: textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurface.withValues(
alpha: 0.6,
),
),
),
),
),
],
),
),
],
),
);
},
);
}
Widget _tab(String label, int index) {
final selected = _tabIndex == index;
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
final disableAnimations = MediaQuery.of(context).disableAnimations;
return GestureDetector(
onTap: () => setState(() => _tabIndex = index),
child: AnimatedContainer(
duration: disableAnimations
? Duration.zero
: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: selected
? colorScheme.primary
: colorScheme.surfaceContainerHighest,
border: Border.all(
color: selected ? colorScheme.primary : colorScheme.outline,
),
borderRadius: BorderRadius.circular(AppSpacing.radiusPill),
),
child: Text(
label,
style: textTheme.labelMedium?.copyWith(
color: selected
? Colors.white
: colorScheme.onSurface.withValues(alpha: 0.7),
),
),
),
);
}
}