lab4 done
This commit is contained in:
parent
c244105079
commit
465d7dce9c
211
lib/main.dart
211
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<MyHomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Тестовые цитаты
|
||||
_quotes.add(Quote(
|
||||
'Сила воли — это ключ к успеху.',
|
||||
'Аноним',
|
||||
@ -75,7 +73,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
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<MyHomePage> {
|
||||
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<MyHomePage> {
|
||||
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) {
|
||||
|
176
lib/test/home_page.dart
Normal file
176
lib/test/home_page.dart
Normal file
@ -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<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
final TextEditingController quoteTextController = TextEditingController();
|
||||
final TextEditingController quoteAuthorController = TextEditingController();
|
||||
|
||||
final List<Quote> _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\"';
|
||||
}
|
||||
}
|
55
lib/test/quote_card.dart
Normal file
55
lib/test/quote_card.dart
Normal file
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user