diff --git a/lib/domain/card.dart b/lib/domain/card.dart new file mode 100644 index 0000000..3a96a32 --- /dev/null +++ b/lib/domain/card.dart @@ -0,0 +1,8 @@ +class CardPostData { + final String name; + final String description; + final String? imageUrl; + final bool isLiked; + + const CardPostData(this.name, this.description, this.imageUrl, this.isLiked); +} diff --git a/lib/main.dart b/lib/main.dart index ea17d3e..41a8c23 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:pmu/presentation/home_page/home_page.dart'; void main() { runApp(const MyApp()); @@ -9,180 +10,9 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: const MyHomePage(title: 'Знакомства'), + return const MaterialApp( + title: 'Meets', + home: MyHomePage(title: 'Знакомства'), ); } } - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - 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 = [ - const _CardPostData( - "Евгения", - "Люблю программирование и кататься на скейте", - "https://avatar.iran.liara.run/public/girl", - true), - const _CardPostData("Алекс", "Junior Flutter Engineer", - "https://avatar.iran.liara.run/public/36", false), - const _CardPostData( - "Станислав", - "Нет ничего приятнее прогулки на природе!", - "https://avatar.iran.liara.run/public/boy", - true), - const _CardPostData("Вероника", "В свободное время хожу на уроки вокала", - "https://avatar.iran.liara.run/public/93", false), - ]; - return Center( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: data.map((e) => _CardPost.fromData(e)).toList(), - ))); - } -} - -class _CardPost extends StatelessWidget { - final String description; - final String? imageUrl; - final bool isLiked; - final String name; - - const _CardPost(this.name, this.description, this.imageUrl, this.isLiked); - - factory _CardPost.fromData(_CardPostData data) => - _CardPost(data.name, data.description, data.imageUrl, data.isLiked); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(10), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.blue, - border: Border.all( - color: Colors.blue, - width: 0.5, - ), - boxShadow: const [ - BoxShadow( - color: Colors.black12, - offset: Offset( - 0.0, - 5.0, - ), - blurRadius: 4.0, - spreadRadius: 1.0, - ), - ], - ), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Text( - name, - style: const TextStyle( - color: Colors.white, - fontSize: 30, - ), - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.only(top: 6.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Text( - description, - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.white, - fontSize: 24, - ), - ), - ), - ], - ), - ), - if (imageUrl != null) - Padding( - padding: const EdgeInsets.all(8.0), - child: Image.network(imageUrl!, - height: 500, width: double.infinity, fit: BoxFit.fitWidth), - ), - if (isLiked) - const Padding( - padding: EdgeInsets.all(4.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.favorite, color: Colors.red, size: 80), - ], - ), - ) - else - const Padding( - padding: EdgeInsets.all(4.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.favorite_border, color: Colors.red, size: 80), - ], - ), - ) - ], - ), - ); - } -} - -class _CardPostData extends StatelessWidget { - final String name; - final String description; - final String? imageUrl; - final bool isLiked; - - const _CardPostData(this.name, this.description, this.imageUrl, this.isLiked); - - @override - Widget build(BuildContext context) { - return const Placeholder(); - } -} diff --git a/lib/presentation/detail_pages/card_detail_page.dart b/lib/presentation/detail_pages/card_detail_page.dart new file mode 100644 index 0000000..ebd244e --- /dev/null +++ b/lib/presentation/detail_pages/card_detail_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:pmu/domain/card.dart'; + +class DetailPage extends StatelessWidget { + final CardPostData data; + + const DetailPage(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: 4.0), + child: Image.network( + data.imageUrl ?? '', + fit: BoxFit.cover, + width: double.infinity, + ), + ), + Padding( + padding: const EdgeInsets.all(4.0), + child: Text( + data.name, + style: const TextStyle(color: Colors.black, fontSize: 30), + ), + ), + Padding( + padding: const EdgeInsets.all(4.0), + child: Text( + data.description, + style: const TextStyle(color: Colors.black, fontSize: 20), + ), + ), + ], + ), + ); + } +} \ 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..59aeb86 --- /dev/null +++ b/lib/presentation/home_page/card.dart @@ -0,0 +1,145 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:pmu/domain/card.dart'; + +typedef OnLikeCallback = void Function(String title, bool isLiked)?; + +const double NORMAL_ICON_SCALE = 2.0; +const double SCALED_ICON_SCALE = 2.5; + +class CardPost extends StatefulWidget { + final String description; + final String? imageUrl; + final bool isLiked; + final String name; + + final OnLikeCallback onLike; + final VoidCallback? onTap; + + const CardPost(this.name, this.description, this.imageUrl, this.isLiked, + this.onLike, this.onTap); + + factory CardPost.fromData(CardPostData data, + {OnLikeCallback onLike, VoidCallback? onTap}) => + CardPost(data.name, data.description, data.imageUrl, data.isLiked, onLike, + onTap); + + @override + State createState() => _CardPostState(); +} + +class _CardPostState extends State { + bool isLiked = false; + + double iconScale = NORMAL_ICON_SCALE; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onTap, + child: Container( + margin: const EdgeInsets.all(10), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.blue, + border: Border.all( + color: Colors.blue, + width: 0.5, + ), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + offset: Offset( + 0.0, + 5.0, + ), + blurRadius: 4.0, + spreadRadius: 1.0, + ), + ], + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + widget.name, + style: const TextStyle( + color: Colors.white, + fontSize: 30, + ), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 6.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + widget.description, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 24, + ), + ), + ), + ], + ), + ), + if (widget.imageUrl != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: Image.network(widget.imageUrl!, + height: 500, + width: double.infinity, + fit: BoxFit.fitWidth), + ), + Padding( + padding: const EdgeInsets.all(4.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => { + setState(() { + isLiked = !isLiked; + iconScale = SCALED_ICON_SCALE; + + Timer(const Duration(milliseconds: 110), () { + setState(() { + iconScale = NORMAL_ICON_SCALE; + }); + }); + widget.onLike?.call(widget.name, isLiked); + }) + }, + child: AnimatedScale( + scale: iconScale, + duration: const Duration(milliseconds: 250), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + child: isLiked + ? const Icon( + Icons.favorite, + color: Colors.red, + key: ValueKey(1), + ) + : const Icon(Icons.favorite_border, + color: Colors.black, 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..f8d3f6f --- /dev/null +++ b/lib/presentation/home_page/home_page.dart @@ -0,0 +1,86 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:pmu/domain/card.dart'; +import 'package:pmu/presentation/detail_pages/card_detail_page.dart'; +import 'package:pmu/presentation/home_page/card.dart'; + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + 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 _navToDetails(BuildContext context, CardPostData data) { + Navigator.push( + context, + CupertinoPageRoute(builder: (context) => DetailPage(data)), + ); + } + + void _showSnackBar(BuildContext context, String title, bool isLiked) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + '${isLiked ? 'Вы поставили лайк: ' : 'Вы убрали лайк: '} $title', + style: const TextStyle( + color: Colors.black45, + fontSize: 20, + fontWeight: FontWeight.bold + ), + ), + backgroundColor: isLiked ? Colors.green : Colors.red, + duration: const Duration(seconds: 2), + )); + }); + } + + @override + Widget build(BuildContext context) { + final data = [ + const CardPostData( + "Евгения", + "Люблю программирование и кататься на скейте", + "https://avatar.iran.liara.run/public/girl", + false), + const CardPostData("Алекс", "Junior Flutter Engineer", + "https://avatar.iran.liara.run/public/36", false), + const CardPostData( + "Станислав", + "Нет ничего приятнее прогулки на природе!", + "https://avatar.iran.liara.run/public/boy", + false), + const CardPostData("Вероника", "В свободное время хожу на уроки вокала", + "https://avatar.iran.liara.run/public/93", false), + ]; + return Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: data.map((data) { + return CardPost.fromData(data, + onLike: (String title, bool isLiked) => + _showSnackBar(context, title, isLiked), + onTap: () => _navToDetails(context, data)); + }).toList(), + ))); + } +}