diff --git a/lib/domain/models/card.dart b/lib/domain/models/card.dart new file mode 100644 index 0000000..8e3eef3 --- /dev/null +++ b/lib/domain/models/card.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class CardDate{ + final String text; + final String descriptionText; + final IconData icon; + final String? imageUrl; + + CardDate( + this.text, + { + required this.descriptionText, + this.icon = Icons.ac_unit_outlined, + this.imageUrl = 'https://via.placeholder.com/150', + }); +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 2ceed87..b589d21 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,5 @@ -import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter_labs_app/presentation/home_page/home_page.dart'; void main() { runApp(const MyApp()); @@ -7,7 +7,7 @@ void main() { class MyApp extends StatelessWidget { const MyApp({super.key}); - + @override Widget build(BuildContext context) { return MaterialApp( @@ -21,169 +21,3 @@ 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 { - Color _color = Colors.limeAccent; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: _color, - title: Text(widget.title), - ), - body: const MyWidget(), - ); - } -} - -class _CardDate{ - final String text; - final String descriptionText; - final IconData icon; - final String? imageUrl; - - _CardDate( - this.text, - { - required this.descriptionText, - this.icon = Icons.ac_unit_outlined, - this.imageUrl, - }); - -} - -class MyWidget extends StatelessWidget{ - const MyWidget({super.key}); - - @override - Widget build(BuildContext context) { - final data = [ - _CardDate( - 'Hi', - descriptionText: 'hello', - icon: Icons.h_mobiledata_sharp, - imageUrl: 'https://avatars.mds.yandex.net/i?id=7cb577fccf8b7354b5248cb8101dd09433fa521f-4253662-images-thumbs&n=13', - ), - _CardDate( - 'Privet', - descriptionText: 'hello', - icon: Icons.pages, - imageUrl: 'https://avatars.mds.yandex.net/i?id=34f57633c955c47b56c68537076e5bfabb4a397b-4577841-images-thumbs&n=13', - ), - _CardDate( - 'Arigato', - descriptionText: 'hello', - icon: Icons.account_tree_sharp, - imageUrl: 'https://avatars.mds.yandex.net/i?id=d3e8f5e5c373aec40e18fdb5bc28f98367e0f0d9-4271037-images-thumbs&n=13', - ), - ]; - - return Center( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: data.map((e) => _Card.fromData(e)).toList(), - ), - ) - ); - } -} - -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(_CardDate data) => _Card( - data.text, - descriptionText: data.descriptionText, - icon: data.icon, - imageUrl: data.imageUrl, - ); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white70, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: Colors.grey, - width: 2, - ), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(.5), - spreadRadius: 4, - offset: const Offset(0, 5), - blurRadius: 8, - blurStyle: BlurStyle.normal - ) - ] - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(20), - child: SizedBox( - height: 140, - width: 100, - child: Image.network( - imageUrl ?? '', - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => const Placeholder(), - ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - text, - style: Theme.of(context).textTheme.headlineLarge, - //softWrap: false, - ), - Text( - descriptionText, - style: Theme.of(context).textTheme.bodyLarge - ), - ], - ), - ), - ), - //const Spacer(), - 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..022e3df --- /dev/null +++ b/lib/presentation/details_page/details_page.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_labs_app/domain/models/card.dart'; + +class DetailsPage extends StatelessWidget{ + final CardDate 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 ?? '', + width: double.infinity, + fit: BoxFit.cover, + ), + ), + 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, + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/home_page/card.dart b/lib/presentation/home_page/card.dart new file mode 100644 index 0000000..be51115 --- /dev/null +++ b/lib/presentation/home_page/card.dart @@ -0,0 +1,144 @@ +part of 'home_page.dart'; + +typedef OnLikeCallback = void Function(String title, bool isLiked)?; + +class _Card extends StatefulWidget { + final String text; + final String descriptionText; + final IconData icon; + final String? imageUrl; + final OnLikeCallback onLike; + final VoidCallback? onTap; + + const _Card( + this.text, + { + required this.descriptionText, + this.icon = Icons.ac_unit_outlined, + this.imageUrl, + this.onLike, + this.onTap + } + ); + + factory _Card.fromData( + CardDate data, { + OnLikeCallback onLike, + VoidCallback? onTap, + }) => _Card( + data.text, + descriptionText: data.descriptionText, + icon: data.icon, + 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: Container( + margin: const EdgeInsets.all(16), + constraints: const BoxConstraints(minHeight: 140), + decoration: BoxDecoration( + color: Colors.white70, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.grey, + width: 2, + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(.5), + spreadRadius: 4, + offset: const Offset(0, 5), + blurRadius: 8, + blurStyle: BlurStyle.normal + ) + ] + ), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(18), + topLeft: Radius.circular(18), + ), + child: SizedBox( + height: double.infinity, + width: 120, + child: Image.network( + widget.imageUrl ?? '', + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 8.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 + ), + ], + ), + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Icon(widget.icon), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + 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), + ), + ), + ), + ), + ], + ), + ], + ), + ) + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/home_page/home_page.dart b/lib/presentation/home_page/home_page.dart new file mode 100644 index 0000000..3bfe416 --- /dev/null +++ b/lib/presentation/home_page/home_page.dart @@ -0,0 +1,91 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_labs_app/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.limeAccent; + + @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 = [ + CardDate( + 'Hi', + descriptionText: 'hello', + icon: Icons.h_mobiledata_sharp, + imageUrl: 'https://avatars.mds.yandex.net/i?id=7cb577fccf8b7354b5248cb8101dd09433fa521f-4253662-images-thumbs&n=13', + ), + CardDate( + 'Privet', + descriptionText: 'hello', + icon: Icons.pages, + imageUrl: 'https://avatars.mds.yandex.net/i?id=34f57633c955c47b56c68537076e5bfabb4a397b-4577841-images-thumbs&n=13', + ), + CardDate( + 'Arigato', + descriptionText: 'hello', + icon: Icons.account_tree_sharp, + imageUrl: 'https://avatars.mds.yandex.net/i?id=d3e8f5e5c373aec40e18fdb5bc28f98367e0f0d9-4271037-images-thumbs&n=13', + ), + CardDate( + 'Last', + descriptionText: 'hello', + ) + ]; + + return Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: data.map((data) => _Card.fromData( + data, + onLike: (String title, bool isLiked) => _showSnacBar(context, title, isLiked), + onTap: () => _navToDetails(context, data), + )).toList(), + ), + ) + ); + } + + void _navToDetails(BuildContext context, CardDate data){ + Navigator.push(context, CupertinoPageRoute(builder: (context) => DetailsPage(data)),); + } + + void _showSnacBar(BuildContext context, String title, bool isLiked){ + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'You ${isLiked ? 'liked ' : 'disliked '} $title!', + style: Theme.of(context).textTheme.bodyLarge, + ), + backgroundColor: Colors.limeAccent, + duration: const Duration(seconds: 1), + )); + }); + } +} \ No newline at end of file