From 465d7dce9cb3b9fc315eaa7de0703f027ecc4b32 Mon Sep 17 00:00:00 2001 From: Yourdax Date: Wed, 27 Nov 2024 13:34:01 +0400 Subject: [PATCH] lab4 done --- lib/main.dart | 211 +++++++++++++++++++++++---------------- lib/test/home_page.dart | 176 ++++++++++++++++++++++++++++++++ lib/test/quote_card.dart | 55 ++++++++++ 3 files changed, 357 insertions(+), 85 deletions(-) create mode 100644 lib/test/home_page.dart create mode 100644 lib/test/quote_card.dart diff --git a/lib/main.dart b/lib/main.dart index 0818472..4cbcad4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,3 @@ -// ignore_for_file: unnecessary_string_escapes - import 'package:flutter/material.dart'; void main() { @@ -22,16 +20,18 @@ class MyApp extends StatelessWidget { } } -// Модель цитаты class Quote { final String text; final String author; final String imagePath; + bool isFavorite; - Quote(this.text, this.author, this.imagePath); + 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; @@ -51,8 +51,6 @@ class _MyHomePageState extends State { @override void initState() { super.initState(); - - // Тестовые цитаты _quotes.add(Quote( 'Сила воли — это ключ к успеху.', 'Аноним', @@ -75,7 +73,7 @@ class _MyHomePageState extends State { final newQuote = Quote( quoteTextController.text.addQuotesIfMissing().capitalize(), quoteAuthorController.text.capitalize(), - defaultImagePath, // Устанавливаем дефолтное изображение + defaultImagePath, ); _quotes.add(newQuote); quoteTextController.clear(); @@ -97,65 +95,95 @@ class _MyHomePageState extends State { backgroundColor: Theme.of(context).colorScheme.primaryContainer, foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer, ), - body: Stack( + body: Column( children: [ - Column( - children: [ - const Padding( - padding: EdgeInsets.all(16.0), - child: Text( - 'Список цитат', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.normal), - ), - ), - Expanded( - child: _quotes.isEmpty - ? const Center( - child: Text( - 'Нет добавленных цитат. Добавьте первую!', - style: TextStyle(fontSize: 18, color: Colors.grey), - ), - ) - : ListView.builder( - itemCount: _quotes.length, - itemBuilder: (context, index) { - final quote = _quotes[index]; - 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: const Icon(Icons.delete), - onPressed: () => _removeQuote(index), - ), - ), - ); - }, - ), - ), - ], + const Padding( + padding: EdgeInsets.all(16.0), + child: Text( + 'Список цитат', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.normal), + ), ), - Align( - alignment: Alignment.bottomCenter, - child: QuoteCounter(count: _quotes.length), + Expanded( + child: _quotes.isEmpty + ? const Center( + child: Text( + 'Нет добавленных цитат. Добавьте первую!', + style: TextStyle(fontSize: 18, color: Colors.grey), + ), + ) + : ListView.builder( + itemCount: _quotes.length, + itemBuilder: (context, index) { + final quote = _quotes[index]; + 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: Row( + mainAxisSize: MainAxisSize.min, + children: [ + 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), + ), + ); + }, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + _removeQuote(index); + }, + ), + ], + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + QuoteDetailScreen(quote: quote), + ), + ); + }, + ), + + ); + }, + ), ), ], ), @@ -200,40 +228,53 @@ class _MyHomePageState extends State { tooltip: 'Добавить цитату', child: const Icon(Icons.format_quote), ), + //floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, ); } } -// Виджет счетчика цитат -class QuoteCounter extends StatelessWidget { - final int count; - const QuoteCounter({super.key, required this.count}); +class QuoteDetailScreen extends StatelessWidget { + final Quote quote; + const QuoteDetailScreen({super.key, required this.quote}); @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Container( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - decoration: BoxDecoration( - color: Colors.grey.withOpacity(0.7), - borderRadius: BorderRadius.circular(15), - border: Border.all( - color: Colors.black.withOpacity(0.2), - width: 1, + 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), + ), + ], ), ), - child: Text( - 'Количество цитат: $count', - style: const TextStyle( - fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black), - ), ), ); } } -// Расширение для форматирования текста extension StringExtension on String { String capitalize() { return split(' ').map((word) { diff --git a/lib/test/home_page.dart b/lib/test/home_page.dart new file mode 100644 index 0000000..7a5cfef --- /dev/null +++ b/lib/test/home_page.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import './quote_card.dart'; + +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 HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + final TextEditingController quoteTextController = TextEditingController(); + final TextEditingController quoteAuthorController = TextEditingController(); + + final List _quotes = []; + final String defaultImagePath = + 'https://cdn-icons-png.flaticon.com/128/17818/17818874.png'; + + @override + void initState() { + super.initState(); + _quotes.addAll([ + Quote( + 'Сила воли — это ключ к успеху.', + 'Аноним', + 'https://cdn-icons-png.flaticon.com/128/17818/17818880.png', + ), + Quote( + 'Вера в себя — это первый шаг к победе.', + 'Аноним', + 'https://cdn-icons-png.flaticon.com/128/17818/17818889.png', + ), + Quote( + 'Не бойся быть отличным, бойся быть обычным.', + 'Аноним', + 'https://cdn-icons-png.flaticon.com/128/17818/17818899.png', + ), + ]); + } + + void _addQuote() { + setState(() { + final newQuote = Quote( + quoteTextController.text.addQuotesIfMissing().capitalize(), + quoteAuthorController.text.capitalize(), + defaultImagePath, + ); + _quotes.add(newQuote); + quoteTextController.clear(); + quoteAuthorController.clear(); + }); + } + + void _removeQuote(int index) { + setState(() { + _quotes.removeAt(index); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Цитаты'), + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer, + ), + body: Column( + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: Text( + 'Список цитат', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.normal), + ), + ), + Expanded( + child: _quotes.isEmpty + ? const Center( + child: Text( + 'Нет добавленных цитат. Добавьте первую!', + style: TextStyle(fontSize: 18, color: Colors.grey), + ), + ) + : ListView.builder( + itemCount: _quotes.length, + itemBuilder: (context, index) { + return QuoteCard( + quote: _quotes[index], + onDelete: () => _removeQuote(index), + onFavoriteToggle: () { + setState(() { + _quotes[index].toggleFavorite(); + }); + }, + ); + }, + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Новая цитата'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: quoteTextController, + decoration: const InputDecoration( + hintText: 'Введите текст цитаты', + ), + ), + const SizedBox(height: 8), + TextField( + controller: quoteAuthorController, + decoration: const InputDecoration( + hintText: 'Введите автора цитаты', + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + _addQuote(); + Navigator.of(context).pop(); + }, + child: const Text('Добавить'), + ), + ], + ); + }, + ); + }, + tooltip: 'Добавить цитату', + child: const Icon(Icons.format_quote), + ), + ); + } +} + +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\"'; + } +} diff --git a/lib/test/quote_card.dart b/lib/test/quote_card.dart new file mode 100644 index 0000000..64c5e68 --- /dev/null +++ b/lib/test/quote_card.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import './home_page.dart'; + +class QuoteCard extends StatelessWidget { + final Quote quote; + final VoidCallback onDelete; + final VoidCallback onFavoriteToggle; + + const QuoteCard({ + super.key, + required this.quote, + required this.onDelete, + 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: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + quote.isFavorite ? Icons.favorite : Icons.favorite_border, + color: quote.isFavorite ? Colors.red : null, + ), + onPressed: onFavoriteToggle, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: onDelete, + ), + ], + ), + ), + ); + } +}