Files
bookshelf/books_flutter/lib/screens/book_details_screen.dart
2026-02-08 12:04:45 +06:00

299 lines
11 KiB
Dart
Raw Permalink 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/book_bloc.dart';
import '../bloc/book/book_event.dart';
import '../models/models.dart';
import '../theme/app_theme.dart';
import '../theme/app_spacing.dart';
import 'add_book_screen.dart';
class BookDetailsScreen extends StatelessWidget {
final Book book;
const BookDetailsScreen({super.key, required this.book});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
final statusLabel = switch (book.status) {
'reading' => 'Читаю',
'done' => 'Прочитано',
'want_to_read' => 'Хочу прочитать',
_ => book.status,
};
return SingleChildScrollView(
child: Material(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Hero section
Stack(
children: [
if (book.coverUrl != null)
SizedBox(
height: 300,
width: double.infinity,
child: ShaderMask(
shaderCallback: (rect) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [colorScheme.surface, Colors.transparent],
).createShader(rect),
blendMode: BlendMode.dstIn,
child: Image.network(book.coverUrl!, fit: BoxFit.cover),
),
),
SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(
AppSpacing.sm,
AppSpacing.sm,
AppSpacing.sm,
0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {},
),
],
),
),
),
Positioned.fill(
child: Align(
alignment: Alignment.bottomCenter,
child: Hero(
tag: 'book-cover-${book.id}',
child: Container(
width: 140,
height: 210,
margin: const EdgeInsets.only(bottom: 0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
AppSpacing.radiusLarge,
),
boxShadow: AppTheme.shadowXl,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(
AppSpacing.radiusLarge,
),
child: book.coverUrl != null
? Image.network(book.coverUrl!, fit: BoxFit.cover)
: Container(
color: colorScheme.surfaceContainerHighest,
child: Center(
child: Icon(
Icons.book,
color: colorScheme.primary.withValues(
alpha: 0.3,
),
size: 48,
),
),
),
),
),
),
),
),
],
),
const SizedBox(height: AppSpacing.md),
// Status badge
Center(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: colorScheme.primaryContainer,
border: Border.all(color: colorScheme.primary),
borderRadius: BorderRadius.circular(AppSpacing.radiusPill),
),
child: Text(
statusLabel,
style: textTheme.labelMedium?.copyWith(
color: colorScheme.primary,
),
),
),
),
const SizedBox(height: AppSpacing.md),
// Title & Author
Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(book.title, style: textTheme.displayMedium),
const SizedBox(height: AppSpacing.xs),
Text(
book.author,
style: textTheme.titleLarge?.copyWith(
color: colorScheme.onSurface.withValues(alpha: 0.7),
),
),
const SizedBox(height: AppSpacing.md),
// Genre tag
Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest,
border: Border.all(color: colorScheme.outline),
borderRadius: BorderRadius.circular(AppSpacing.radiusSmall),
),
child: Text(book.genre, style: textTheme.labelMedium),
),
const SizedBox(height: AppSpacing.lg),
// Action buttons
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => AddBookScreen(editBook: book),
),
);
},
icon: const Icon(Icons.edit, size: 18),
label: const Text('Изменить'),
),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: OutlinedButton.icon(
onPressed: () {
context.read<BookBloc>().add(DeleteBook(book.id));
Navigator.pop(context);
},
icon: const Icon(Icons.delete_outline, size: 18),
label: const Text('Удалить'),
style: OutlinedButton.styleFrom(
foregroundColor: colorScheme.error,
side: BorderSide(color: colorScheme.error),
),
),
),
],
),
const SizedBox(height: AppSpacing.lg),
// About
Text('О книге', style: textTheme.headlineMedium),
const SizedBox(height: AppSpacing.sm),
Text(book.annotation, style: textTheme.bodyLarge),
const SizedBox(height: AppSpacing.lg),
// Info grid
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: AppSpacing.md,
crossAxisSpacing: AppSpacing.md,
childAspectRatio: 2.5,
children: [
_infoTile(
context,
Icons.menu_book,
Colors.blue,
'Страницы',
'${book.pages ?? ""}',
),
_infoTile(
context,
Icons.language,
Colors.purple,
'Язык',
book.language ?? '',
),
_infoTile(
context,
Icons.calendar_month,
Colors.orange,
'Год',
'${book.publishedYear ?? ""}',
),
_infoTile(
context,
Icons.star,
Colors.amber,
'Рейтинг',
'${book.rating ?? ""}',
),
],
),
const SizedBox(height: 40),
],
),
),
],
),
),
);
}
Widget _infoTile(
BuildContext context,
IconData icon,
Color color,
String label,
String value,
) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: colorScheme.surface,
border: Border.all(color: colorScheme.outline),
borderRadius: BorderRadius.circular(AppSpacing.radiusMedium),
boxShadow: AppTheme.shadowSm,
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppSpacing.radiusSmall),
),
child: Icon(icon, color: color, size: 22),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(label, style: textTheme.labelSmall),
Text(
value,
style: textTheme.titleSmall,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
);
}
}