lab6 done
This commit is contained in:
parent
76dcbb954b
commit
1c46c1c0fb
20
lib/components/utils/debounce.dart
Normal file
20
lib/components/utils/debounce.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
class Debounce {
|
||||
factory Debounce() => _instance;
|
||||
|
||||
Debounce._();
|
||||
|
||||
static final Debounce _instance = Debounce._();
|
||||
|
||||
static Timer? _timer;
|
||||
|
||||
static void run(
|
||||
VoidCallback action, {
|
||||
Duration delay = const Duration(milliseconds: 1000),
|
||||
}) {
|
||||
_timer?.cancel();
|
||||
_timer = Timer(delay, action);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import '../../domain/quote.dart';
|
||||
import '/data/dtos/quotes_dto.dart';
|
||||
import '/main.dart';
|
||||
import '/presentation/home_page/home_page.dart';
|
||||
|
||||
const _imagePlaceholder =
|
||||
'https://cdn-icons-png.flaticon.com/128/17818/17818874.png';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import '/main.dart';
|
||||
import '../../domain/quote.dart';
|
||||
import '/presentation/home_page/home_page.dart';
|
||||
|
||||
typedef OnErrorCallback = void Function(String? error);
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../domain/quote.dart';
|
||||
import '/data/dtos/quotes_dto.dart';
|
||||
import '/data/mappers/quotes_mapper.dart';
|
||||
import '/data/repositories/api_interface.dart';
|
||||
import '/main.dart';
|
||||
import '/data/presentation/dialogs/show_dialog.dart';
|
||||
import '/presentation/home_page/home_page.dart';
|
||||
import '/presentation/dialogs/show_dialog.dart';
|
||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||
|
||||
class QuotesRepository extends ApiInterface {
|
||||
@ -23,12 +24,12 @@ class QuotesRepository extends ApiInterface {
|
||||
try {
|
||||
final Map<String, dynamic> queryParams = {};
|
||||
|
||||
// Добавляем фильтрацию по тексту цитаты
|
||||
|
||||
if (q != null && q.isNotEmpty) {
|
||||
queryParams['filter'] = q; // Фильтруем по слову
|
||||
queryParams['filter'] = q;
|
||||
}
|
||||
|
||||
// Добавляем фильтрацию по автору
|
||||
|
||||
if (author != null && author.isNotEmpty) {
|
||||
queryParams['type'] = 'author';
|
||||
queryParams['filter'] = author;
|
||||
|
12
lib/domain/quote.dart
Normal file
12
lib/domain/quote.dart
Normal file
@ -0,0 +1,12 @@
|
||||
class Quote {
|
||||
final String text;
|
||||
final String author;
|
||||
final String imagePath;
|
||||
bool isFavorite;
|
||||
|
||||
Quote(this.text, this.author, this.imagePath, [this.isFavorite = false]);
|
||||
|
||||
void toggleFavorite() {
|
||||
isFavorite = !isFavorite;
|
||||
}
|
||||
}
|
270
lib/main.dart
270
lib/main.dart
@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '/data/repositories/quotes_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'data/repositories/quotes_repository.dart';
|
||||
import '/presentation/home_page/bloc/bloc.dart';
|
||||
import '/presentation/home_page/home_page.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
@ -16,266 +19,15 @@ class MyApp extends StatelessWidget {
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const MyHomePage(title: 'Цитаты'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Quote {
|
||||
final String text;
|
||||
final String author;
|
||||
final String imagePath;
|
||||
bool isFavorite;
|
||||
|
||||
Quote(this.text, this.author, this.imagePath, [this.isFavorite = false]);
|
||||
|
||||
void toggleFavorite() {
|
||||
isFavorite = !isFavorite;
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
final List<Quote> _quotes = [];
|
||||
final List<Quote> _filteredQuotes = [];
|
||||
|
||||
final QuotesRepository _quotesRepository = QuotesRepository();
|
||||
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
final TextEditingController authorController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadQuotes();
|
||||
|
||||
searchController.addListener(() {
|
||||
_loadQuotes();
|
||||
});
|
||||
|
||||
authorController.addListener(() {
|
||||
_loadQuotes();
|
||||
});
|
||||
}
|
||||
|
||||
// Метод для загрузки цитат с учетом обоих фильтров
|
||||
Future<void> _loadQuotes() async {
|
||||
final query = searchController.text;
|
||||
final author = authorController.text;
|
||||
|
||||
final quotes = await _quotesRepository.loadData(
|
||||
q: query, // Поиск по цитате
|
||||
author: author, // Поиск по автору
|
||||
onError: (error) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Ошибка загрузки: $error')),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (quotes != null) {
|
||||
setState(() {
|
||||
_quotes.clear();
|
||||
_quotes.addAll(quotes);
|
||||
_filterQuotes();
|
||||
});
|
||||
}
|
||||
print('Загружено цитат: ${_quotes.length}');
|
||||
print('Отфильтрованные цитаты: ${_filteredQuotes.length}');
|
||||
}
|
||||
|
||||
// Фильтрация цитат по введенным значениям
|
||||
void _filterQuotes() {
|
||||
setState(() {
|
||||
_filteredQuotes.clear(); // Очистите список фильтрации
|
||||
_filteredQuotes.addAll(_quotes); // Изначально показываем все цитаты
|
||||
|
||||
// Применяем фильтрацию по цитате
|
||||
if (searchController.text.isNotEmpty) {
|
||||
_filteredQuotes.retainWhere((quote) =>
|
||||
quote.text.toLowerCase().contains(searchController.text.toLowerCase()));
|
||||
}
|
||||
|
||||
// Применяем фильтрацию по автору
|
||||
if (authorController.text.isNotEmpty) {
|
||||
_filteredQuotes.retainWhere((quote) =>
|
||||
quote.author.toLowerCase().contains(authorController.text.toLowerCase()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Цитаты'),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Поиск по цитате',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: TextField(
|
||||
controller: authorController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Поиск по автору',
|
||||
prefixIcon: Icon(Icons.person),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _filteredQuotes.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
'Нет цитат для отображения.',
|
||||
style: TextStyle(fontSize: 18, color: Colors.grey),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: _filteredQuotes.length,
|
||||
itemBuilder: (context, index) {
|
||||
final quote = _filteredQuotes[index]; // Используем _filteredQuotes для отображения
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 10.0,
|
||||
),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.all(8.0),
|
||||
leading: SizedBox(
|
||||
width: 50.0,
|
||||
child: Image.network(
|
||||
quote.imagePath,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) =>
|
||||
const Icon(Icons.error, color: Colors.red),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
quote.text,
|
||||
style: const TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text('- ${quote.author}'),
|
||||
trailing: IconButton(
|
||||
icon: Icon(
|
||||
quote.isFavorite
|
||||
? Icons.favorite
|
||||
: Icons.favorite_border,
|
||||
color: quote.isFavorite ? Colors.red : null,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
quote.toggleFavorite();
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(quote.isFavorite
|
||||
? 'Цитата добавлена в избранное'
|
||||
: 'Цитата удалена из избранного'),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
QuoteDetailScreen(quote: quote),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class QuoteDetailScreen extends StatelessWidget {
|
||||
final Quote quote;
|
||||
|
||||
const QuoteDetailScreen({super.key, required this.quote});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Детали цитаты'),
|
||||
),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.network(
|
||||
quote.imagePath,
|
||||
height: 150,
|
||||
errorBuilder: (_, __, ___) =>
|
||||
const Icon(Icons.error, color: Colors.red),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
quote.text,
|
||||
style:
|
||||
const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'- ${quote.author}',
|
||||
style: const TextStyle(fontSize: 18, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
home: RepositoryProvider<QuotesRepository>(
|
||||
lazy: true,
|
||||
create: (_) => QuotesRepository(),
|
||||
child: BlocProvider<HomeBloc>(
|
||||
lazy: false,
|
||||
create: (context) => HomeBloc(context.read<QuotesRepository>()),
|
||||
child: const MyHomePage(title: "Цитаты",),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
extension StringExtension on String {
|
||||
String capitalize() {
|
||||
return split(' ').map((word) {
|
||||
if (word.isNotEmpty) {
|
||||
return '${word[0].toUpperCase()}${word.substring(1).toLowerCase()}';
|
||||
}
|
||||
return word;
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
String addQuotesIfMissing() {
|
||||
if (startsWith('\"') && endsWith('\"')) return this;
|
||||
if (startsWith('\"') && !endsWith('\"')) return '$this\"';
|
||||
if (endsWith('\"') && !startsWith('\"')) return '\"$this';
|
||||
return '\"$this\"';
|
||||
}
|
||||
}
|
||||
|
51
lib/presentation/home_page/bloc/bloc.dart
Normal file
51
lib/presentation/home_page/bloc/bloc.dart
Normal file
@ -0,0 +1,51 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../data/repositories/quotes_repository.dart';
|
||||
import '/presentation/home_page/bloc/events.dart';
|
||||
import '/presentation/home_page/bloc/state.dart';
|
||||
|
||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
final QuotesRepository repo;
|
||||
|
||||
HomeBloc(this.repo) : super(const HomeState()) {
|
||||
on<HomeLoadDataEvent>(_onLoadData);
|
||||
on<HomeRefreshEvent>(_onRefreshData);
|
||||
}
|
||||
|
||||
void _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
|
||||
final author = event.author?.trim() ?? '';
|
||||
final search = event.search?.trim() ?? '';
|
||||
|
||||
// Если поле автора не заполнено, загружаем данные из репозитория
|
||||
if (author.isEmpty) {
|
||||
emit(state.copyWith(
|
||||
data: repo.loadData(q: search).then((result) => result ?? []), // Обработка null
|
||||
));
|
||||
} else {
|
||||
// Если автор указан, фильтруем данные по тексту цитаты
|
||||
try {
|
||||
final currentData = await repo.loadData(q:search, author: author,
|
||||
);
|
||||
final filteredData = currentData?.where((quote) {
|
||||
final matchesAuthor = quote.author.toLowerCase().contains(author.toLowerCase());
|
||||
final matchesSearch = search.isEmpty ||
|
||||
quote.text.toLowerCase().contains(search.toLowerCase());
|
||||
return matchesAuthor && matchesSearch;
|
||||
}).toList();
|
||||
emit(state.copyWith(data: Future.value(filteredData)));
|
||||
} catch (error) {
|
||||
emit(state.copyWith(data: Future.error(error)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Future<void> _onRefreshData(HomeRefreshEvent event, Emitter<HomeState> emit) async {
|
||||
add(HomeLoadDataEvent(search: event.search, author: event.author)); // Просто перезапускаем загрузку
|
||||
}
|
||||
|
||||
}
|
17
lib/presentation/home_page/bloc/events.dart
Normal file
17
lib/presentation/home_page/bloc/events.dart
Normal file
@ -0,0 +1,17 @@
|
||||
abstract class HomeEvent {
|
||||
const HomeEvent();
|
||||
}
|
||||
|
||||
class HomeLoadDataEvent extends HomeEvent {
|
||||
final String? search;
|
||||
final String? author;
|
||||
|
||||
const HomeLoadDataEvent({this.search, this.author});
|
||||
}
|
||||
|
||||
class HomeRefreshEvent extends HomeEvent {
|
||||
final String? search;
|
||||
final String? author;
|
||||
|
||||
const HomeRefreshEvent({this.search, this.author});
|
||||
}
|
15
lib/presentation/home_page/bloc/state.dart
Normal file
15
lib/presentation/home_page/bloc/state.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../../domain/quote.dart';
|
||||
import '../home_page.dart';
|
||||
|
||||
|
||||
class HomeState extends Equatable {
|
||||
final Future<List<Quote>>? data; // Изменено
|
||||
|
||||
const HomeState({this.data});
|
||||
|
||||
HomeState copyWith({Future<List<Quote>>? data}) => HomeState(data: data ?? this.data);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [data];
|
||||
}
|
98
lib/presentation/home_page/card.dart
Normal file
98
lib/presentation/home_page/card.dart
Normal file
@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../domain/quote.dart';
|
||||
import 'home_page.dart';
|
||||
|
||||
class QuoteCard extends StatelessWidget {
|
||||
final Quote quote;
|
||||
final VoidCallback onFavoriteToggle;
|
||||
|
||||
const QuoteCard({
|
||||
super.key,
|
||||
required this.quote,
|
||||
required this.onFavoriteToggle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 10.0,
|
||||
),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.all(8.0),
|
||||
leading: SizedBox(
|
||||
width: 50.0,
|
||||
child: Image.network(
|
||||
quote.imagePath,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) =>
|
||||
const Icon(Icons.error, color: Colors.red),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
quote.text,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text('- ${quote.author}'),
|
||||
trailing: IconButton(
|
||||
icon: Icon(
|
||||
quote.isFavorite ? Icons.favorite : Icons.favorite_border,
|
||||
color: quote.isFavorite ? Colors.red : null,
|
||||
),
|
||||
onPressed: onFavoriteToggle,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => QuoteDetailScreen(quote: quote),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class QuoteDetailScreen extends StatelessWidget {
|
||||
final Quote quote;
|
||||
|
||||
const QuoteDetailScreen({super.key, required this.quote});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Детали цитаты'),
|
||||
),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.network(
|
||||
quote.imagePath,
|
||||
height: 150,
|
||||
errorBuilder: (_, __, ___) =>
|
||||
const Icon(Icons.error, color: Colors.red),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
quote.text,
|
||||
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'- ${quote.author}',
|
||||
style: const TextStyle(fontSize: 18, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
138
lib/presentation/home_page/home_page.dart
Normal file
138
lib/presentation/home_page/home_page.dart
Normal file
@ -0,0 +1,138 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../components/utils/debounce.dart';
|
||||
import '../../domain/quote.dart';
|
||||
import '/data/repositories/quotes_repository.dart';
|
||||
import '/presentation/home_page/bloc/bloc.dart';
|
||||
import '/presentation/home_page/bloc/events.dart';
|
||||
import '/presentation/home_page/bloc/state.dart';
|
||||
import 'card.dart';
|
||||
|
||||
class MyHomePage extends StatelessWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => HomeBloc(QuotesRepository())..add(const HomeLoadDataEvent()),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
),
|
||||
body: const _HomePageBody(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomePageBody extends StatefulWidget {
|
||||
const _HomePageBody();
|
||||
|
||||
@override
|
||||
State<_HomePageBody> createState() => _HomePageBodyState();
|
||||
}
|
||||
|
||||
class _HomePageBodyState extends State<_HomePageBody> {
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
final TextEditingController authorController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
searchController.addListener(() {
|
||||
Debounce.run(() {
|
||||
context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text));
|
||||
});
|
||||
});
|
||||
|
||||
authorController.addListener(() {
|
||||
Debounce.run(() {
|
||||
context.read<HomeBloc>().add(HomeLoadDataEvent(
|
||||
search: searchController.text,
|
||||
author: authorController.text,
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<HomeBloc, HomeState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Поиск по цитате',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: TextField(
|
||||
controller: authorController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Поиск по автору',
|
||||
prefixIcon: Icon(Icons.person),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: state.data == null
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: FutureBuilder<List<Quote>?>(
|
||||
future: state.data,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('Ошибка: ${snapshot.error}'));
|
||||
}
|
||||
if (snapshot.data == null || snapshot.data!.isEmpty) {
|
||||
return const Center(
|
||||
child: Text(
|
||||
'Нет цитат для отображения.',
|
||||
style: TextStyle(fontSize: 18, color: Colors.grey),
|
||||
),
|
||||
);
|
||||
}
|
||||
final quotes = snapshot.data!;
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<HomeBloc>().add(HomeRefreshEvent(
|
||||
search: searchController.text,
|
||||
author: authorController.text,
|
||||
));
|
||||
},
|
||||
child: ListView.builder(
|
||||
itemCount: quotes.length,
|
||||
itemBuilder: (context, index) {
|
||||
final quote = quotes[index];
|
||||
return QuoteCard(
|
||||
quote: quote,
|
||||
onFavoriteToggle: () {
|
||||
setState(() {
|
||||
quote.toggleFavorite();
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user