diff --git a/lib/domain/models/carddata.dart b/lib/domain/models/carddata.dart new file mode 100644 index 0000000..e1201e5 --- /dev/null +++ b/lib/domain/models/carddata.dart @@ -0,0 +1,10 @@ + +class CardData { + final String text; + final String descriptionText; + final String? imageUrl; + + CardData(this.text, + {required this.descriptionText, + this.imageUrl}); +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 78d1aa0..6869e27 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:pmu_labs/presentation/home_page/home_page.dart'; void main() { runApp(const MyApp()); @@ -36,145 +37,8 @@ class MyApp extends StatelessWidget { } } -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - final String title; - @override - State createState() => _MyHomePageState(); -} -class _MyHomePageState extends State { - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: Text(widget.title), - ), - body: const MyWidget(), - ); - } -} - -class MyWidget extends StatelessWidget { - const MyWidget({super.key}); - - @override - Widget build(BuildContext context) { - final data = [ - _CardData( - 'Зов Ктулху', - descriptionText: 'Лучше бы не находить разгадку, объединяющую сошедших с ума творцов...', - imageUrl: 'https://lovecraft.country/images/bibliography/call-of-cthulhu-mini.webp' - ), - _CardData( - 'Хребты безумия', - descriptionText: '«Хребты безумия» написаны в документальной манере повествования, постепенно привыкая к которой, становишься свидетелем особой реальности описываемых событий.', - icon: Icons.hail, - imageUrl: 'https://lovecraft.country/images/bibliography/At-the-Mountains-of-Madness-mini.webp' - ), - _CardData('Тень над Инсмутом', descriptionText: 'Инсмут, маленький рыбацкий городок неподалеку от Аркхэма, уже много лет имеет дурную славу. В округе ходят жуткие истории о его угрюмых и уродливых жителях, от которых лучше держаться подальше.', icon: Icons.warning_amber, imageUrl: 'https://lovecraft.country/images/bibliography/The-Shadow-over-Innsmouth.webp') - ]; - return Center( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: data.map((e) => _Card.fromData(e)).toList(), - ), - ), - ); - } -} - -class _CardData { - final String text; - final String descriptionText; - final IconData icon; - final String? imageUrl; - - _CardData(this.text, - {required this.descriptionText, - this.icon = Icons.ac_unit_outlined, - this.imageUrl}); -} - -class _Card extends StatelessWidget { - final String text; - final String descriptionText; - final IconData icon; - final String? imageUrl; - - const _Card(this.text, - {required this.descriptionText, - this.icon = Icons.ac_unit_outlined, - this.imageUrl}); - - factory _Card.fromData(_CardData data) => _Card( - data.text, - descriptionText: data.descriptionText, - icon: data.icon, - imageUrl: data.imageUrl, - ); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.only(top: 16), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.amber, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: Colors.grey, - width: 2, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(20), - child: SizedBox( - width: 140, - height: 100, - child: Image.network( - imageUrl ?? '', - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => const Placeholder(), - ), - ), - ), - Flexible( - child: Padding( - padding: const EdgeInsets.only(left: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(text, style: Theme.of(context).textTheme.headlineLarge), - Text(descriptionText, - style: Theme.of(context).textTheme.bodyLarge), - ], - ), - )), - Padding( - padding: const EdgeInsets.only(left:8.0), - child: Icon(icon), - ), - ], - ), - ); - } -} diff --git a/lib/presentation/details_page/details_page.dart b/lib/presentation/details_page/details_page.dart new file mode 100644 index 0000000..2001eff --- /dev/null +++ b/lib/presentation/details_page/details_page.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:pmu_labs/domain/models/carddata.dart'; + +class DetailsPage extends StatelessWidget { + final CardData data; + + const DetailsPage(this.data, {super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Image.network( + data.imageUrl ?? '', + )), + + Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: Text( + data.text, + style: Theme.of(context).textTheme.headlineLarge, + )), + Text( + data.descriptionText, + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ); + } +} diff --git a/lib/presentation/home_page/card.dart b/lib/presentation/home_page/card.dart new file mode 100644 index 0000000..d911741 --- /dev/null +++ b/lib/presentation/home_page/card.dart @@ -0,0 +1,103 @@ +part of 'home_page.dart'; + +class _CardState extends State<_Card> { + bool isLiked = false; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onTap, + child: Container( + margin: const EdgeInsets.only(top: 16), + constraints: const BoxConstraints(minHeight: 140), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.amber, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.grey, + width: 2, + ), + ), + child: IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(20), + child: SizedBox( + width: 140, + height: 100, + child: Image.network( + widget.imageUrl ?? '', + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => const Placeholder(), + ), + ), + ), + Flexible( + child: Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.text, + style: Theme.of(context).textTheme.headlineLarge), + Text(widget.descriptionText, + style: Theme.of(context).textTheme.bodyLarge), + ], + ), + )), + Padding( + padding: + const EdgeInsets.only(left: 8.0, right: 16, bottom: 16), + child: GestureDetector( + onTap: () { + setState(() { + isLiked = !isLiked; + }); + widget.onLike?.call(widget.text, isLiked); + }, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: isLiked + ? const Icon( + Icons.favorite, + color: Colors.redAccent, + key: ValueKey(0), + ) + : const Icon(Icons.favorite_border), + key: ValueKey(1), + ), + )) + ], + ), + ), + ), + ); + } +} + +typedef onLikeCallback = void Function(String title, bool isliked)?; + +class _Card extends StatefulWidget { + final String text; + final String descriptionText; + final String? imageUrl; + final onLikeCallback onLike; + final VoidCallback? onTap; + + const _Card(this.text, + {required this.descriptionText, this.imageUrl, this.onLike, this.onTap}); + + factory _Card.fromData(CardData data, {onLikeCallback onLike, VoidCallback? onTap}) => + _Card(data.text, + descriptionText: data.descriptionText, + imageUrl: data.imageUrl, + onLike: onLike, + onTap: onTap); + + @override + State<_Card> createState() => _CardState(); +} diff --git a/lib/presentation/home_page/home_page.dart b/lib/presentation/home_page/home_page.dart new file mode 100644 index 0000000..ad28383 --- /dev/null +++ b/lib/presentation/home_page/home_page.dart @@ -0,0 +1,95 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:pmu_labs/presentation/details_page/details_page.dart'; + +import '../../domain/models/carddata.dart'; + +part 'card.dart'; + + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme + .of(context) + .colorScheme + .inversePrimary, + title: Text(widget.title), + ), + body: const Body(), + ); + } + + + +} + +class Body extends StatelessWidget { + const Body({super.key}); + + void _showSnackbar(BuildContext context, String title, bool isLiked) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Лайк на $title ${isLiked ? 'поставлен':'убран'}', + style: Theme.of(context).textTheme.bodyLarge, + ), + backgroundColor: Colors.orangeAccent, + duration: const Duration(seconds: 1), + )); + }); + } + void _navToDetails(BuildContext context, CardData data) + { + Navigator.push(context, CupertinoPageRoute(builder: (context) => DetailsPage(data))); + } + + @override + Widget build(BuildContext context) { + final data = [ + CardData('Зов Ктулху', + descriptionText: + 'Лучше бы не находить разгадку, объединяющую сошедших с ума творцов...', + imageUrl: + 'https://lovecraft.country/images/bibliography/call-of-cthulhu-mini.webp'), + CardData('Хребты безумия', + descriptionText: + '«Хребты безумия» написаны в документальной манере повествования, постепенно привыкая к которой, становишься свидетелем особой реальности описываемых событий.', + imageUrl: + 'https://lovecraft.country/images/bibliography/At-the-Mountains-of-Madness-mini.webp'), + CardData('Тень над Инсмутом', + descriptionText: + 'Инсмут, маленький рыбацкий городок неподалеку от Аркхэма, уже много лет имеет дурную славу. В округе ходят жуткие истории о его угрюмых и уродливых жителях, от которых лучше держаться подальше.', + imageUrl: + 'https://lovecraft.country/images/bibliography/The-Shadow-over-Innsmouth.webp') + ]; + return Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: data.map((e) => _Card.fromData(e,onLike: (title, isLiked) => _showSnackbar(context, title, isLiked), onTap: () => _navToDetails(context, e))).toList(), + ), + ), + ); + } +} \ No newline at end of file