diff --git a/lib/domain/models/card.dart b/lib/domain/models/card.dart new file mode 100644 index 0000000..30e7729 --- /dev/null +++ b/lib/domain/models/card.dart @@ -0,0 +1,13 @@ +class CardData { + final String text; + final String descriptionText; + final String? imageUrl; + final String gameDesc; + + CardData( + this.text, { + required this.descriptionText, + this.imageUrl, + required this.gameDesc, + }); +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index d7e71fb..9c55300 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'presentation/home_page/home_page.dart'; void main() { runApp(const MyApp()); @@ -13,7 +14,7 @@ class MyApp extends StatelessWidget { title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange), + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurpleAccent), useMaterial3: true, ), home: const MyHomePage(title: 'Яковлев Максим Григорьевич'), @@ -21,141 +22,6 @@ class MyApp extends StatelessWidget { } } -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - final String title; - @override - State createState() => _MyHomePageState(); -} -class _MyHomePageState extends State { - final Color _color = Colors.deepPurpleAccent; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: _color, - title: Text(widget.title), - ), - body: const MyWidget(), - ); - } -} - -class MyWidget extends StatelessWidget { - const MyWidget({super.key}); - - @override - Widget build(BuildContext context) { - final data = [ - _CartData( - 'Warhammer 40000:Space Marine 2', - descriptionText: 'Читать новость', - imageUrl: - 'https://news.xbox.com/en-us/wp-content/uploads/sites/2/2021/12/SpaceMarine2_TemporaryArtwork_4K_logo.jpg', - ), - _CartData('Risk of rain 2', - descriptionText: 'Читать новость', - imageUrl: - 'https://digiseller.mycdn.ink/preview/990130/p1_3675944_49000546.jpg'), - _CartData( - 'Linage 2', - descriptionText: 'Читать новость', - imageUrl: 'https://avatars.mds.yandex.net/i?id=67f86892afc08862da2521e5b4cf4439_l-9569150-images-thumbs&n=13' - ) - ]; - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: data.map((e) => _Card.fromData(e)).toList(), - ), - ); - } -} - -class _CartData { - final String text; - final String descriptionText; - - //final IconData icon; - final String? imageUrl; - - _CartData( - this.text, { - required this.descriptionText, - //required this.icon, - this.imageUrl, - }); -} - -class _Card extends StatelessWidget { - final String text; - final String descriptionText; - - //final IconData icon; - final String? imageUrl; - - const _Card( - this.text, { - //required this.icon, - required this.descriptionText, - this.imageUrl, - }); - - factory _Card.fromData(_CartData data) => _Card( - data.text, - descriptionText: data.descriptionText, - //icon: data.icon, - imageUrl: data.imageUrl, - ); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Container( - margin: const EdgeInsets.only(top: 16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.orangeAccent, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: Colors.grey, width: 2)), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 140, - width: 150, - child: Image.network( - imageUrl ?? '', - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => const Placeholder(), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - text, - style: Theme.of(context).textTheme.titleLarge, - ), - Text( - descriptionText, - style: Theme.of(context).textTheme.bodyLarge, - ) - ], - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/presentation/details_page/details_page.dart b/lib/presentation/details_page/details_page.dart new file mode 100644 index 0000000..0606226 --- /dev/null +++ b/lib/presentation/details_page/details_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:pmu_labs/domain/models/card.dart'; + +class DetailsPage extends StatelessWidget { + final CardData data; + + const DetailsPage(this.data, {super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(backgroundColor: Colors.deepPurple, + ), + body: SingleChildScrollView( + child: Container( + constraints: BoxConstraints( minHeight: MediaQuery.sizeOf(context).height - 105), + decoration: const BoxDecoration(color: Colors.deepPurpleAccent,), + padding: const EdgeInsets.all(10), + child: 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.gameDesc, + 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..fb45048 --- /dev/null +++ b/lib/presentation/home_page/card.dart @@ -0,0 +1,153 @@ +part of 'home_page.dart'; + +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(); +} + +class _CardState extends State<_Card> { + bool isLiked = false; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Container( + margin: const EdgeInsets.only(top: 16), + //padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.deepPurpleAccent, + borderRadius: BorderRadius.circular(20), + ), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), + topLeft: Radius.circular(20), + ), + child: SizedBox( + height: 160, + width: 160, + child: Stack( + children: [ + Positioned.fill( + child: Image.network( + widget.imageUrl ?? '', + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => const Placeholder(), + ), + ), + Align( + alignment: Alignment.topLeft, + child: Container( + decoration: const BoxDecoration( + color: Colors.purpleAccent, + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(20), + ), + ), + padding: const EdgeInsets.fromLTRB(8, 2, 8, 2), + child: Text( + 'Новинка!', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: Colors.black), + ), + ), + ) + ], + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.text, + style: Theme.of(context).textTheme.titleLarge, + ), + Text( + widget.descriptionText, + style: Theme.of(context).textTheme.bodyLarge, + ) + ], + ), + ), + ), + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only( + left: 8.0, + right: 15, + bottom: 15, + ), + 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.purpleAccent, + key: ValueKey(0), + ) + : const Icon( + Icons.favorite_border, + key: ValueKey(1), + ), + ), + ), + ), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/home_page/home_page.dart b/lib/presentation/home_page/home_page.dart new file mode 100644 index 0000000..23f6bcb --- /dev/null +++ b/lib/presentation/home_page/home_page.dart @@ -0,0 +1,100 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:pmu_labs/presentation/details_page/details_page.dart'; + +import '../../domain/models/card.dart'; + +part 'card.dart'; + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + final Color _color = Colors.deepPurpleAccent; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: _color, + title: Text(widget.title), + ), + body: const Body(), + ); + } +} + +class Body extends StatelessWidget { + const Body({super.key}); + + @override + Widget build(BuildContext context) { + final data = [ + CardData('Warhammer 40000:Space Marine 2', + descriptionText: 'Читать новость', + imageUrl: + 'https://news.xbox.com/en-us/wp-content/uploads/sites/2/2021/12/SpaceMarine2_TemporaryArtwork_4K_logo.jpg', + gameDesc: 'Обретите сверхчеловеческую мощь космодесантника. Пустите в ход смертоносные навыки и разрушительное оружие, чтобы ' + + ' истребить безжалостных тиранидов. Защитите Империум в ярких одиночных боях или многопользовательских режимах с ' + + 'видом от третьего лица. В продолжении экшена 2011 года Space Marine вам предстоит сразиться с врагами человечества и' + + ' вновь доказать ему свою преданность в роли лейтенанта Деметрия Тита, который вернулся в ряды Ультрамаринов. Дайте отпор ' + + 'ужасам Галактики в эпических боях сразу на нескольких планетах. Раскройте мрачные секреты и отбросьте тень вечной ночи.'), + CardData('Risk of rain 2', + descriptionText: 'Читать новость', + imageUrl: + 'https://digiseller.mycdn.ink/preview/990130/p1_3675944_49000546.jpg', + gameDesc: 'Вас ожидает более тысячи созданных вручную областей. Каждая из них кишит смертельно опасными монстрами и огромными боссами,' + + ' стремящимися оборвать вашу жизнь. Дойдите до финального босса и выберитесь с планеты, либо продолжите своё приключение,' + + ' чтобы выяснить, как долго вы сможете продержаться. Благодаря умной системе повышения уровня ваша сила и сила ваших врагов будет постоянно расти во время игры.'), + CardData('Yakuza 0', + descriptionText: 'Читать новость', + imageUrl: + 'https://avatars.mds.yandex.net/get-marketpic/1588570/pic3d2a9b85ee47202578aac1bb1f97fcc0/orig', + gameDesc: 'В декабре 1988 года двое героев из мира якудза — Кадзума Кирю (молодой якудза низшего ранга из региона Канто)' + + ' и Горо Мадзима (менеджер кабаре, бывший якудза из региона Кансай, изгнанный из клана, но желающий во что бы' + + ' то ни стало вернуться) — оказываются втянуты в борьбу за небольшой «пустой участок» земли в центре района Камуротё,' + + ' который притягивает преступные силы со всей Японии.Сюжетная линия игры разбита на 2 линии, которые косвенно пересекаются по ходу истории') + ]; + return Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: data.map((data) { + return _Card.fromData( + data, + onLike: (String title, bool isLiked) => + _showSnackBar(context, title, isLiked), + onTap: () => _navToDetails(context, data), + ); + }).toList(), + ), + ), + ); + } + + void _navToDetails(BuildContext context, CardData data) { + Navigator.push( + context, + CupertinoPageRoute(builder: (context) => DetailsPage(data)), + ); + } + + void _showSnackBar(BuildContext context, String title, bool isLiked) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'News $title ${isLiked ? 'liked' : 'disliked '}', + style: Theme.of(context).textTheme.bodyLarge, + ), + backgroundColor: Colors.deepPurpleAccent, + duration: const Duration(seconds: 1), + )); + }); + } +}