{"id":400465,"date":"2024-06-29T15:16:59","date_gmt":"2024-06-29T15:16:59","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=400465"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=400465","title":{"rendered":"<span>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043b\u043e\u043d Instagram \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 Flutter \u0438 Feeds. \u0427\u0430\u0441\u0442\u044c 3<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/6fa\/616\/491\/6fa616491084ace6d173bb14cdfaacb0.jpg\" width=\"1200\" height=\"630\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6fa\/616\/491\/6fa616491084ace6d173bb14cdfaacb0.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442! \u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u044e \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u0441\u0442\u0430\u0442\u044c\u0438, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u043a\u0430\u043a \u043e\u0441\u043d\u043e\u0432\u0443 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043e\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430 \u0432 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435\u00a0<a href=\"https:\/\/dom24x7.ru\/\" rel=\"noopener noreferrer nofollow\">Dom24x7<\/a>, \u0433\u0434\u0435 \u043b\u044e\u0434\u0438 \u043c\u043e\u0433\u0443\u0442 \u043e\u0431\u0449\u0430\u0442\u044c\u0441\u044f \u0434\u0440\u0443\u0433 \u0441 \u0434\u0440\u0443\u0433\u043e\u043c, \u0440\u0435\u0448\u0430\u0442\u044c \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0449\u0438\u0435 \u0431\u044b\u0442\u043e\u0432\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u0423\u041a\/\u0422\u0421\u0416. \u041f\u0435\u0440\u0432\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0441\u0442\u0430\u0442\u044c\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/727794\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>, \u0430 \u0432\u0442\u043e\u0440\u0443\u044e \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 <a href=\"https:\/\/habr.com\/ru\/articles\/730104\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>.<\/p>\n<p>\u0418\u0442\u0430\u043a&#8230;<\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0438\u043a\u043e\u043d\u043a\u0443 \u0443\u0433\u0430\u0441\u0430\u043d\u0438\u044f<\/h2>\n<p>\u041f\u0440\u0438\u0448\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0434\u043b\u044f \u0435\u0449\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430. \u042d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0432\u0438\u0434\u0436\u0435\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0443\u0433\u0430\u0441\u0430\u043d\u0438\u0435 \u0438\u043a\u043e\u043d\u043a\u0438, \u0438\u043b\u0438 \u043d\u0430\u0441\u044b\u0449\u0435\u043d\u0438\u0435 \u0435\u0435 \u0446\u0432\u0435\u0442\u0430, \u043f\u0443\u0442\u0435\u043c \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043d\u0430 \u043d\u0435\u0435.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 <code>components\/app_widgets\/tap_fade_icon.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code class=\"dart\">import 'package:flutter\/material.dart';  \/\/\/ {@template tap_fade_icon} \/\/\/ A tappable icon that fades colors when tapped and held. \/\/\/ {@endtemplate} class TapFadeIcon extends StatefulWidget {   \/\/\/ {@macro tap_fade_icon}   const TapFadeIcon({     Key? key,     required this.onTap,     required this.icon,     required this.iconColor,     this.size = 22,   }) : super(key: key);    \/\/\/ Callback to handle tap.   final VoidCallback onTap;    \/\/\/ Color of the icon.   final Color iconColor;    \/\/\/ Type of icon.   final IconData icon;    \/\/\/ Icon size.   final double size;    @override   _TapFadeIconState createState() => _TapFadeIconState(); }  class _TapFadeIconState extends State&lt;TapFadeIcon> {   late Color color = widget.iconColor;    void handleTapDown(TapDownDetails _) {     setState(() {       color = widget.iconColor.withOpacity(0.7);     });   }    void handleTapUp(TapUpDetails _) {     setState(() {       color = widget.iconColor;     });      widget.onTap(); \/\/ Execute callback.   }    @override   void didUpdateWidget(covariant TapFadeIcon oldWidget) {     super.didUpdateWidget(oldWidget);     if (oldWidget.iconColor != widget.iconColor) {       color = widget.iconColor;     }   }    @override   Widget build(BuildContext context) {     return GestureDetector(       onTapDown: handleTapDown,       onTapUp: handleTapUp,       child: Icon(         widget.icon,         color: color,         size: widget.size,       ),     );   } }<\/code><\/pre>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u044f \u043d\u0430 \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u0432\u0438\u0434\u0436\u0435\u0442\u0430 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u044d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u0432 \u043d\u0443\u0436\u043d\u044b\u0439 \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b. \u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 <code>components\/app_widgets\/app_widgets.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043d\u0438\u0436\u0435\u043b\u0435\u0436\u0430\u0449\u0438\u0439 \u043a\u043e\u0434:<\/p>\n<pre><code class=\"dart\">export 'avatars.dart'; export 'tap_fade_icon.dart'; \/\/ ADD THIS<\/code><\/pre>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443<\/h2>\n<p>\u041a\u0430\u043a \u0443\u0436\u0435 \u0431\u044b\u043b\u043e \u0443\u043f\u043e\u043c\u044f\u043d\u0443\u0442\u043e \u0440\u0430\u043d\u0435\u0435, \u043d\u0430\u0448 \u0438\u043d\u0441\u0442\u0430\u0433\u0440\u0430\u043c \u043a\u043b\u043e\u043d \u043c\u043e\u0436\u043d\u043e \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0440\u0430\u0437\u0431\u0438\u0442\u044c \u043d\u0430 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438: \u043d\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e (<strong>user<\/strong>) \u0438 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e (<strong>timeline<\/strong>) \u043b\u0435\u043d\u0442\u044b. \u041a\u0430\u0436\u0434\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u044e \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443, \u0433\u0434\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u044e\u0442\u0441\u044f \u0432\u0441\u0435 \u043f\u043e\u0441\u0442\u044b, \u0441\u0434\u0435\u043b\u0430\u043d\u043d\u044b\u0435 \u0438\u043c; \u0432 \u0442\u043e \u0432\u0440\u0435\u043c\u044f \u043a\u0430\u043a \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043b\u0435\u043d\u0442\u0430 \u044d\u0442\u043e \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u044f \u0432\u0441\u0435\u0445 \u043b\u0435\u043d\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0432\u044b \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u044b.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0435\u043a\u0446\u0438\u0438, \u0432\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u0442\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u0439 \u043f\u0440\u043e\u0434\u0432\u0438\u0433\u0430\u0442\u044c <strong>\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f<\/strong> (\u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 &#8212; \u043f\u043e\u0441\u0442\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f) \u043f\u0440\u044f\u043c\u0438\u043a\u043e\u043c \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443.<\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u0448 \u043a\u043b\u043e\u043d Instagram \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u043e \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u044f\u043c \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438 \u043a \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u043c\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e, \u043c\u044b \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0434\u043e\u043b\u0436\u043d\u044b \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u044c \u0441 \u043a\u043e\u0434\u043e\u043c \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0439 \u043b\u0435\u043d\u0442\u044b. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e:<\/p>\n<ul>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u044d\u043a\u0440\u0430\u043d, \u0433\u0434\u0435 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u043f\u043e\u0441\u0442\u044b;<\/p>\n<\/li>\n<li>\n<p>\u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c <strong>PictureViewer<\/strong> (hero \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438);<\/p>\n<\/li>\n<li>\n<p>\u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c <strong>AppBar<\/strong> (\u0432\u0435\u0440\u0445\u043d\u044e\u044e \u043f\u0430\u043d\u0435\u043b\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f) \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u0447\u0435\u043c \u043e\u0434\u043d\u0443 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044e \u0432 \u043f\u0440\u043e\u0444\u0438\u043b\u044c;<\/p>\n<\/li>\n<li>\n<p>\u0432\u043d\u0435\u0434\u0440\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0438 \u043e\u0442\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043b\u0435\u043d\u0442 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0420\u0430\u0437\u043e\u0431\u0440\u0430\u0432\u0448\u0438\u0441\u044c \u0441\u043e \u0432\u0441\u0435\u043c \u044d\u0442\u0438\u043c, \u0434\u0430\u043b\u0435\u0435, \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043b\u0435\u043d\u0442\u044b.<\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0440\u0430\u043d \u00ab\u041d\u043e\u0432\u0430\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f\u00bb<\/h2>\n<p>\u042d\u0442\u043e \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u044d\u043a\u0440\u0430\u043d, \u0433\u0434\u0435 \u0432\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0435 \u043d\u043e\u0432\u043e\u0435 \u0444\u043e\u0442\u043e \u0432 \u0441\u0432\u043e\u0439 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043f\u043e\u0434\u043f\u0438\u0441\u044c\u044e \u043a \u043d\u0435\u043c\u0443.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b <code>components\/new_post\/new_post_screen.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u043d\u0435\u0433\u043e:<\/p>\n<pre><code class=\"dart\">import 'dart:io'; import 'package:flutter\/material.dart'; import 'package:image_picker\/image_picker.dart'; import 'package:stream_feed_flutter_core\/stream_feed_flutter_core.dart'; import 'package:transparent_image\/transparent_image.dart';  import '..\/..\/app\/app.dart'; import '..\/app_widgets\/app_widgets.dart';  \/\/\/ Screen to choose photos and add a new feed post. class NewPostScreen extends StatefulWidget {   \/\/\/ Create a [NewPostScreen].   const NewPostScreen({Key? key}) : super(key: key);    \/\/\/ Material route to this screen.   static Route get route =>       MaterialPageRoute(builder: (_) => const NewPostScreen());    @override   _NewPostScreenState createState() => _NewPostScreenState(); }  class _NewPostScreenState extends State&lt;NewPostScreen> {   static const double maxImageHeight = 1000;   static const double maxImageWidth = 800;    final _formKey = GlobalKey&lt;FormState>();   final _text = TextEditingController();    XFile? _pickedFile;   bool loading = false;    final picker = ImagePicker();    Future&lt;void> _pickFile() async {     _pickedFile = await picker.pickImage(       source: ImageSource.gallery,       maxHeight: maxImageHeight,       maxWidth: maxImageWidth,       imageQuality: 70,     );     setState(() {});   }    Future&lt;void> _postImage() async {     if (_pickedFile == null) {       context.removeAndShowSnackbar('Please select an image first');       return;     }      if (!_formKey.currentState!.validate()) {       context.removeAndShowSnackbar('Please enter a caption');       return;     }     _setLoading(true);      final client = context.appState.client;      var decodedImage =         await decodeImageFromList(await _pickedFile!.readAsBytes());      final imageUrl =         await client.images.upload(AttachmentFile(path: _pickedFile!.path));      if (imageUrl != null) {       final _resizedUrl = await client.images.getResized(         imageUrl,         const Resize(300, 300),       );        if (_resizedUrl != null &amp;&amp; client.currentUser != null) {         await FeedProvider.of(context).bloc.onAddActivity(           feedGroup: 'user',           verb: 'post',           object: 'image',           data: {             'description': _text.text,             'image_url': imageUrl,             'resized_image_url': _resizedUrl,             'image_width': decodedImage.width,             'image_height': decodedImage.height,             'aspect_ratio': decodedImage.width \/ decodedImage.height           },         );       }     }      _setLoading(false, shouldCallSetState: false);     context.removeAndShowSnackbar('Post created!');      Navigator.of(context).pop();   }    void _setLoading(bool state, {bool shouldCallSetState = true}) {     if (loading != state) {       loading = state;       if (shouldCallSetState) {         setState(() {});       }     }   }    @override   void dispose() {     _text.dispose();     super.dispose();   }    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         leading: TapFadeIcon(           onTap: () => Navigator.pop(context),           icon: Icons.close,           iconColor: Theme.of(context).appBarTheme.iconTheme!.color!,         ),         actions: [           Padding(             padding: const EdgeInsets.all(8.0),             child: Center(               child: GestureDetector(                 onTap: _postImage,                 child: const Text('Share', style: AppTextStyle.textStyleAction),               ),             ),           )         ],       ),       body: loading           ? Center(               child: Column(                 mainAxisAlignment: MainAxisAlignment.center,                 children: const [                   CircularProgressIndicator(),                   SizedBox(height: 12),                   Text('Uploading...')                 ],               ),             )           : ListView(               children: [                 InkWell(                   onTap: _pickFile,                   child: SizedBox(                     height: 400,                     child: (_pickedFile != null)                         ? FadeInImage(                             fit: BoxFit.contain,                             placeholder: MemoryImage(kTransparentImage),                             image: Image.file(File(_pickedFile!.path)).image,                           )                         : Container(                             decoration: const BoxDecoration(                               gradient: LinearGradient(                                   begin: Alignment.bottomLeft,                                   end: Alignment.topRight,                                   colors: [                                     AppColors.bottomGradient,                                     AppColors.topGradient                                   ]),                             ),                             height: 300,                             child: const Center(                               child: Text(                                 'Tap to select an image',                                 style: TextStyle(                                   color: AppColors.light,                                   fontSize: 18,                                   shadows: &lt;Shadow>[                                     Shadow(                                       offset: Offset(2.0, 1.0),                                       blurRadius: 3.0,                                       color: Colors.black54,                                     ),                                     Shadow(                                       offset: Offset(1.0, 1.5),                                       blurRadius: 5.0,                                       color: Colors.black54,                                     ),                                   ],                                 ),                               ),                             ),                           ),                   ),                 ),                 const SizedBox(                   height: 22,                 ),                 Form(                   key: _formKey,                   child: Padding(                     padding: const EdgeInsets.all(8.0),                     child: TextFormField(                       controller: _text,                       decoration: const InputDecoration(                         hintText: 'Write a caption',                         border: InputBorder.none,                       ),                       validator: (text) {                         if (text == null || text.isEmpty) {                           return 'Caption is empty';                         }                         return null;                       },                     ),                   ),                 ),               ],             ),     );   } }<\/code><\/pre>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u0434 \u0432 \u0434\u0435\u0442\u0430\u043b\u044f\u0445:<\/p>\n<ul>\n<li>\n<p>\u043f\u043e \u0442\u043e\u043c\u0443 \u0436\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0443 \u043a\u0430\u043a \u0438 \u0440\u0430\u043d\u0435\u0435, \u0437\u0434\u0435\u0441\u044c, \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u0430\u043a\u0435\u0442 <strong>image_picker<\/strong> \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435. \u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u043d\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043e, \u043c\u044b \u0437\u0430\u0434\u0430\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 _pickedFile \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439;<\/p>\n<\/li>\n<li>\n<p>\u0432 \u044d\u0442\u043e\u0439 \u0441\u0435\u043a\u0446\u0438\u0438 \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c <strong>TextFormField<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043e\u0442 \u043d\u0430\u0441 \u0432\u0432\u043e\u0434\u0430 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u0441\u0442\u0430;<\/p>\n<\/li>\n<li>\n<p>\u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043e \u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0432\u0432\u0435\u0434\u0435\u043d\u043e, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u0436\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443<strong> Share<\/strong>, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u043b\u043e\u0436\u0438\u0442\u044c \u0441\u0432\u043e\u0439 \u043f\u043e\u0441\u0442. \u0415\u0441\u043b\u0438 \u044d\u0442\u0438 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u043d\u0435 \u0431\u044b\u043b\u0438 \u0441\u043e\u0431\u043b\u044e\u0434\u0435\u043d\u043e, \u0442\u043e \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043d\u043e\u043f\u043a\u0430 <strong>Share<\/strong> \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 <code>_postImage<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ol>\n<li>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 <strong>true<\/strong> \u0434\u043b\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>\u0414\u0435\u043a\u043e\u0434\u0438\u0440\u0443\u0435\u0442 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0442\u0430\u043a,\u00a0 \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u043e \u043f\u0440\u0438\u043d\u044f\u043b\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043d\u0430\u043c\u0438 \u0440\u0430\u0437\u043c\u0435\u0440;<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 Stream CDN;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 <code>getResized<\/code> \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <code>FeedProvider.of(context).bloc<\/code>, \u0447\u0442\u043e\u0431\u044b \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0438\u0442\u044c <strong>FeedBloc<\/strong> \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u043f\u043e\u0441\u0442 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 <code>onAddActivity<\/code>.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441 \u043d\u0430\u0448\u0438\u043c \u0438\u043d\u0441\u0442\u0430\u0433\u0440\u0430\u043c \u043a\u043b\u043e\u043d\u043e\u043c, \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043d\u0430\u043b\u0438\u0447\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0431: \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u0435 (actor), \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438 (verb) \u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0435 (object). \u0418\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c &#8212; \u044d\u0442\u043e \u0441\u0443\u0431\u044a\u0435\u043a\u0442, \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0449\u0438\u0439 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 (<strong>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c<\/strong>). \u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 &#8212; <strong>\u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u0442<\/strong>. \u041e\u0431\u044a\u0435\u043a\u0442 \u0438\u043b\u0438 \u0442\u043e \u043d\u0430\u0434 \u0447\u0435\u043c \u0441\u043e\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 &#8212; <strong>\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435<\/strong>.<\/p>\n<p>\u041f\u0440\u043e\u0449\u0435 \u0433\u043e\u0432\u043e\u0440\u044f, \u0442\u0435\u043a\u0443\u0449\u0438\u0439 <strong>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u0442 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 <\/strong>(\u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c, \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435, \u043e\u0431\u044a\u0435\u043a\u0442)<strong> <\/strong>\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443. \u0417\u0430\u0442\u0435\u043c, \u043c\u044b \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0435\u0439:<\/p>\n<ul>\n<li>\n<p>\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u0441\u0442\u043e\u0440\u043e\u043d (\u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\u043e \u043f\u043e\u0437\u0436\u0435);<\/p>\n<\/li>\n<li>\n<p>URL \u0430\u0434\u0440\u0435\u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438 \u0435\u0433\u043e \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p>\u0412\u043e\u0442 \u0438 \u0432\u0441\u0435. \u0421\u0442\u0430\u0442\u044c\u044f \u0431\u044b\u043b\u0430 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0438, \u043d\u0430\u0434\u0435\u044e\u0441\u044c, \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0411\u043e\u043b\u044c\u0448\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043f\u0440\u043e \u0442\u043e, \u043a\u0430\u043a \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0432 \u043b\u0435\u043d\u0442\u0443 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0439\u0442\u0438 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439 <a href=\"https:\/\/getstream.io\/activity-feeds\/docs\/react\/adding_activities\/\" rel=\"noopener noreferrer nofollow\">\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438<\/a>. \u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0438 \u043b\u0435\u043d\u0442\u044b \u0431\u044b\u0432\u0430\u044e\u0442 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u044b\u043c\u0438. \u041d\u043e \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u044d\u0442\u043e\u0439 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0434\u0432\u0438\u043d\u0443\u0442\u044b\u043c\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438. \u041e\u0434\u043d\u0430\u043a\u043e \u043f\u043e\u043a\u0430 \u0447\u0442\u043e, \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0433\u043e \u0432\u044b\u0448\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u043f\u043e\u043b\u043d\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e.<\/p>\n<p>\u0412\u0430\u0448 \u044d\u043a\u0440\u0430\u043d \u0441\u0435\u0439\u0447\u0430\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ffe\/26a\/c6f\/ffe26ac6f4620406a412ee2b755f0517.png\" width=\"293\" height=\"633\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ffe\/26a\/c6f\/ffe26ac6f4620406a412ee2b755f0517.png\"\/><\/figure>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b <code>components\/new_post\/new_post.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435:<\/p>\n<pre><code class=\"dart\">export 'new_post_screen.dart';<\/code><\/pre>\n<h2>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044e \u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u043e\u0432 (\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439) \u0432 \u043f\u0440\u043e\u0444\u0438\u043b\u0435<\/h2>\n<p>\u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u0444\u0430\u0439\u043b <code>components\/profile\/profile_page.dart<\/code>.<\/p>\n<p>\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u0432\u0438\u0434\u0436\u0435\u0442 <code>_NoPostsMessage<\/code>, \u0447\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u043a <strong>NewPostScreen <\/strong>(\u044d\u043a\u0440\u0430\u043d\u0443 \u00ab\u041d\u043e\u0432\u0430\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f\u00bb).<\/p>\n<pre><code class=\"dart\">...  import '..\/new_post\/new_post.dart';  ...  class _NoPostsMessage extends StatelessWidget {   const _NoPostsMessage({     Key? key,   }) : super(key: key);    @override   Widget build(BuildContext context) {     return Column(       mainAxisAlignment: MainAxisAlignment.center,       children: [         const Text('This is too empty'),         const SizedBox(height: 12),         ElevatedButton(           onPressed: () {             Navigator.of(context).push(NewPostScreen.route); \/\/ ADD THIS           },           child: const Text('Add a post'),         )       ],     );   } }  ...<\/code><\/pre>\n<p>\u0412 <strong>ProfilePage<\/strong> \u043f\u0440\u0438\u0432\u0435\u0434\u0438\u0442\u0435 \u0432\u0438\u0434\u0436\u0435\u0442 feedBuilder \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0432\u0438\u0434\u0443:<\/p>\n<pre><code class=\"dart\">import 'package:cached_network_image\/cached_network_image.dart';  ...  feedBuilder: (context, activities) {   return RefreshIndicator(     onRefresh: () async {       await FeedProvider.of(context)           .bloc           .currentUser!           .get(withFollowCounts: true);       return FeedProvider.of(context)           .bloc           .queryEnrichedActivities(feedGroup: 'user');     },     child: CustomScrollView(       slivers: [         SliverToBoxAdapter(           child: _ProfileHeader(             numberOfPosts: activities.length,           ),         ),         const SliverToBoxAdapter(           child: _EditProfileButton(),         ),         const SliverToBoxAdapter(           child: SizedBox(height: 24),         ),         SliverGrid(           gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(             crossAxisCount: 3,             crossAxisSpacing: 1,             mainAxisSpacing: 1,           ),           delegate: SliverChildBuilderDelegate(             (context, index) {               final activity = activities[index];               final url =                         activity.extraData!['resized_image_url'] as String;               return CachedNetworkImage(                 key: ValueKey('image-${activity.id}'),                 width: 200,                 height: 200,                 fit: BoxFit.cover,                 imageUrl: url,               );             },             childCount: activities.length,           ),         )       ],     ),   ); },  ...<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 \u0432 \u0432\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f (\u043f\u043e\u0441\u0442\u044b), \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0448\u0438\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443 \u0441 \u0442\u0435\u043c, \u043a\u0430\u043a \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u044d\u0442\u0438 \u043f\u043e\u0441\u0442\u044b \u0432 \u043d\u0430\u0448\u0435\u0439 \u043b\u0435\u043d\u0442\u0435. \u0411\u0438\u043b\u0434\u0435\u0440, \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0432\u044b\u0448\u0435, \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u0442 <strong>CustomScrollView<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u00a0 <strong>_ProfileHe<\/strong>a<strong>der<\/strong>, \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u0432 \u043f\u043e\u043b\u0435 \u041f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 (\u0432\u0432\u0435\u0440\u0445\u0443 \u043f\u0440\u043e\u0444\u0438\u043b\u044f) \u0446\u0438\u0444\u0440\u0443 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0432\u0430\u0448\u0435\u043c\u0443 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0443 \u043f\u043e\u0441\u0442\u043e\u0432 \u0432 \u043f\u0440\u043e\u0444\u0438\u043b\u0435;<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 <strong>_EditProfileButton <\/strong>(\u043a\u043d\u043e\u043f\u043a\u0443 \u00ab\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0444\u0438\u043b\u044c\u00bb);<\/p>\n<\/li>\n<li>\n<p>\u041e\u0431\u0435\u0440\u0442\u044b\u0432\u0430\u0435\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0432 <strong>RefreshIndicator, <\/strong>\u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u043f\u0443\u0442\u0435\u043c \u0441\u0432\u0430\u0439\u043f\u0430 \u0432\u043d\u0438\u0437 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u0440\u043e\u0444\u0438\u043b\u044f \u0441 \u043f\u043e\u0441\u0442\u0430\u043c\u0438 \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u0414\u043e\u0441\u0442\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u043f\u0443\u0442\u0435\u043c \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 <code>currentUser.get<\/code> \u0438 <code>bloc.queryEnrichedActivities<\/code>. \u0414\u0430\u043b\u0435\u0435 <code>queryEnrichedActivities<\/code> \u043e\u0431\u043d\u043e\u0432\u0438\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 FeedBloc \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0439 \u0433\u0440\u0443\u043f\u043f\u044b \u043b\u0435\u043d\u0442.<\/p>\n<\/li>\n<\/ol>\n<p>\u0413\u043e\u0442\u043e\u0432\u043e! \u0422\u0435\u043f\u0435\u0440\u044c \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0434 \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u043d\u0430\u0448\u0438\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0432 \u0441\u0432\u043e\u044e <strong>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443<\/strong> (\u043f\u0440\u043e\u0444\u0438\u043b\u044c). \u0412 \u0432\u0438\u0434\u0435\u043e \u043d\u0438\u0436\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043f\u0440\u0438\u043c\u0435\u0440 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/6456cab8d2201800a1b246e9\" data-style=\"\" id=\"6456cab8d2201800a1b246e9\" width=\"\"><\/div>\n<h2>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u043e\u0432 \u043f\u0440\u0438 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 (PictureViewer)<\/h2>\n<p>\u041f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c \u043a\u0430\u043a \u043c\u044b \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043b\u0435\u043d\u0442\u0435, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0443\u0437\u043d\u0430\u0435\u043c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u044b.<\/p>\n<p>\u0412\u044b \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430 \u0437\u0430\u043c\u0435\u0447\u0430\u043b\u0438 \u0442\u043e, \u043a\u0430\u043a \u043f\u043b\u0430\u0432\u043d\u043e \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043d\u0430 \u043d\u0435\u0433\u043e \u0438\u0437 \u0440\u0435\u0448\u0435\u0442\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c Instagram. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432\u043e\u0441\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u044d\u0442\u043e \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435, \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0449\u0438\u043a \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0438\u0439 \u0432 \u0441\u0435\u0431\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044e hero \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0439 (\u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u043e\u0442 \u043d\u0438\u0437\u043a\u043e\u0433\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043a \u0432\u044b\u0441\u043e\u043a\u043e\u043c\u0443).\u00a0 \u0422\u0430\u043a \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043f\u043b\u0430\u0432\u043d\u0443\u044e \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u043f\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u043f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d, \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0430\u0436\u0430\u0432 \u043d\u0430 \u043d\u0435\u0433\u043e \u0438\u0437 \u0440\u0435\u0448\u0435\u0442\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439. \u0422\u0430\u043a\u0436\u0435 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u043d\u0435\u043c\u0443, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0430\u0435\u043c \u0435\u0433\u043e.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044d\u0442\u043e \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435, \u043c\u044b:<\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c <strong>CustomRectTween<\/strong> \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 hero \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438;<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c <strong>PageRoute<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439, \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0441\u043e\u0437\u0434\u0430\u0435\u0442 <strong>FadeTransition<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c UI \u0432 \u0444\u0430\u0439\u043b\u0435 <code>components\/profile\/profile_page.dart<\/code>, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u0445\u043e\u0434;<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0445\u043e\u0434\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 <strong>CachedNetworkImage<\/strong>, \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043e\u0442 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043d\u0438\u0437\u043a\u043e\u0433\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043a \u0435\u0433\u043e \u0432\u0435\u0440\u0441\u0438\u0438 \u0441 \u0431\u043e\u043b\u0435\u0435 \u0432\u044b\u0441\u043e\u043a\u0438\u043c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c;<\/p>\n<\/li>\n<li>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0432\u0438\u0434\u0436\u0435\u0442 <strong>InteractiveViewer<\/strong> \u0434\u043b\u044f \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0434\u0432\u0443\u043c\u044f \u043f\u0430\u043b\u044c\u0446\u0430\u043c\u0438 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u043d\u0435\u043c\u0443 \u043a\u043e\u0433\u0434\u0430 \u043e\u043d\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0442\u0430\u043a\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438.<\/p>\n<\/li>\n<\/ol>\n<h2>\u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f<\/h2>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c, \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a\u043e\u0432 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u043e\u0432.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b <code>app\/navigation\/custom_rect_tween.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code class=\"dart\">import 'dart:ui';  import 'package:flutter\/widgets.dart';  \/\/\/ {@template custom_rect_tween} \/\/\/ Linear RectTween with a [Curves.easeOut] curve. \/\/\/ \/\/\/ Less dramatic than the regular [RectTween] used in [Hero] animations. \/\/\/ {@endtemplate} class CustomRectTween extends RectTween {   \/\/\/ {@macro custom_rect_tween}   CustomRectTween({     required Rect? begin,     required Rect? end,   }) : super(begin: begin, end: end);    @override   Rect? lerp(double t) {     final elasticCurveValue = Curves.easeOut.transform(t);     if (begin == null || end == null) return null;     return Rect.fromLTRB(       lerpDouble(begin!.left, end!.left, elasticCurveValue)!,       lerpDouble(begin!.top, end!.top, elasticCurveValue)!,       lerpDouble(begin!.right, end!.right, elasticCurveValue)!,       lerpDouble(begin!.bottom, end!.bottom, elasticCurveValue)!,     );   } }<\/code><\/pre>\n<p>\u042d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u0442 <strong>RectTween <\/strong>\u0438<strong> <\/strong>\u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434<strong> lerp<\/strong>.<strong> <\/strong>\u041c\u044b \u0432\u0435\u0440\u043d\u0435\u043c\u0441\u044f \u043a \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0437\u0436\u0435, \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u043c \u0437\u0430\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0443\u044e hero \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e. \u0422\u0435\u0440\u043c\u0438\u043d <strong>lerp <\/strong>\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 <strong>\u0438\u043d\u0442\u0435\u0440\u043f\u043e\u043b\u044f\u0446\u0438\u044e<\/strong> \u043c\u0435\u0436\u0434\u0443 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u043c \u0438 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043d\u0430 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043a\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 (t).<\/p>\n<p>\u0414\u0430\u043b\u0435\u0435, \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b\u00a0 <code>app\/navigation\/hero_dialog_route.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code class=\"dart\">import 'package:flutter\/material.dart';  \/\/\/ {@template hero_dialog_route} \/\/\/ Custom [PageRoute] that creates an overlay dialog (popup effect). \/\/\/ \/\/\/ Best used with a [Hero] animation. \/\/\/ {@endtemplate} class HeroDialogRoute&lt;T> extends PageRoute&lt;T> {   \/\/\/ {@macro hero_dialog_route}   HeroDialogRoute({     required WidgetBuilder builder,     RouteSettings? settings,     bool fullscreenDialog = false,   })  : _builder = builder,         super(settings: settings, fullscreenDialog: fullscreenDialog);    final WidgetBuilder _builder;    @override   bool get opaque => false;    @override   bool get barrierDismissible => true;    @override   Duration get transitionDuration => const Duration(milliseconds: 300);    @override   bool get maintainState => true;    @override   Color get barrierColor => Colors.black54;    @override   Widget buildTransitions(BuildContext context, Animation&lt;double> animation,       Animation&lt;double> secondaryAnimation, Widget child) {     return FadeTransition(opacity: animation, child: child);   }    @override   Widget buildPage(BuildContext context, Animation&lt;double> animation,       Animation&lt;double> secondaryAnimation) {     return _builder(context);   }    @override   String get barrierLabel => 'Hero Dialog Open'; }<\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043a\u043e\u0434\u0430 \u043c\u044b \u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043a\u043b\u0430\u0441\u0441\u043e\u043c <strong>PageRoute,<\/strong> \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043d\u0430\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0445\u043e\u0434 (<strong>FadeTransition<\/strong>) c \u0437\u0430\u0434\u043d\u0438\u043c \u0444\u043e\u043d\u043e\u043c \u0447\u0435\u0440\u043d\u043e\u0433\u043e \u0446\u0432\u0435\u0442\u0430. \u0415\u0441\u0442\u044c \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u043a\u0430\u043a \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0436\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434. \u0421\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0434\u043e\u043b\u044c\u0448\u0435 \u0437\u0430\u0434\u0435\u0440\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u044d\u0442\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435, \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0438\u0433\u0440\u0430\u0442\u044c\u0441\u044f \u0441 \u043d\u0438\u043c \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043e\u043d \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442.\u00a0 \u0420\u0430\u043d\u0435\u0435 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438 <strong>PageRouteBuilder<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430. \u041e\u0434\u043d\u0430\u043a\u043e, \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044e \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b <code>app\/navigation\/navigation.dart<\/code>\u00a0 \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u043d\u0435\u0433\u043e:<\/p>\n<pre><code class=\"dart\">export 'custom_rect_tween.dart'; export 'hero_dialog_route.dart';<\/code><\/pre>\n<p>\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 <code>app\/app.dart<\/code> \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"dart\">export 'state\/state.dart'; export 'theme.dart'; export 'stream_agram.dart'; export 'utils.dart'; export 'navigation\/navigation.dart'; \/\/ ADD THIS<\/code><\/pre>\n<h2>UI<\/h2>\n<p>\u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u0444\u0430\u0439\u043b <code>components\/profile\/profile_page.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u043d\u0435\u0433\u043e \u0432\u0441\u0451 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u0435 \u043d\u0438\u0436\u0435:<\/p>\n<pre><code class=\"dart\">...  class _PictureViewer extends StatelessWidget {   const _PictureViewer({     Key? key,     required this.activity,   }) : super(key: key);    final EnrichedActivity activity;    @override   Widget build(BuildContext context) {     final resizedUrl = activity.extraData!['resized_image_url'] as String?;     final fullSizeUrl = activity.extraData!['image_url'] as String;     final aspectRatio = activity.extraData!['aspect_ratio'] as double?;      return Scaffold(       appBar: AppBar(         elevation: 0,         backgroundColor: Colors.transparent,       ),       extendBodyBehindAppBar: true,       body: InteractiveViewer(         child: Center(           child: Hero(             tag: 'hero-image-${activity.id}',             createRectTween: (begin, end) {               return CustomRectTween(begin: begin, end: end);             },             child: AspectRatio(               aspectRatio: aspectRatio ?? 1,               child: CachedNetworkImage(                 fadeInDuration: Duration.zero,                 placeholder: (resizedUrl != null)                     ? (context, url) => CachedNetworkImage(                           imageBuilder: (context, imageProvider) =>                               DecoratedBox(                             decoration: BoxDecoration(                               image: DecorationImage(                                 image: imageProvider,                                 fit: BoxFit.contain,                               ),                             ),                           ),                           imageUrl: resizedUrl,                         )                     : null,                 imageBuilder: (context, imageProvider) => DecoratedBox(                   decoration: BoxDecoration(                     image: DecorationImage(                       image: imageProvider,                       fit: BoxFit.contain,                     ),                   ),                 ),                 imageUrl: fullSizeUrl,               ),             ),           ),         ),       ),     );   } }<\/code><\/pre>\n<p>\u041a\u043e\u0434 \u0432\u044b\u0448\u0435 &#8212; \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u044b\u0439. \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ul>\n<li>\n<p>\u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043e URL \u0430\u0434\u0440\u0435\u0441\u0430\u0445 \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438 \u0435\u0433\u043e \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u0441\u043e\u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0438 \u0435\u0433\u043e \u0441\u0442\u043e\u0440\u043e\u043d;<\/p>\n<\/li>\n<li>\n<p>\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 <strong>CachedNetworkImage <\/strong>\u0441 URL \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u043f\u043e\u043b\u043d\u043e\u043c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0438, \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0443 <code>placeholder<\/code>\u00a0 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e <strong>CachedNetworkImage <\/strong>\u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0434\u043b\u044f \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0443\u0436\u0435 \u0431\u044b\u043b\u043e \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043e;<\/p>\n<\/li>\n<li>\n<p>\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0432\u0438\u0434\u0436\u0435\u0442 <strong>AspectRatio, <\/strong>\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0442\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f (\u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0438 \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u043d\u043e\u0433\u043e) \u0437\u0430\u043d\u0438\u043c\u0430\u043b\u0438 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u0443\u044e \u043f\u043b\u043e\u0449\u0430\u0434\u044c \u044d\u043a\u0440\u0430\u043d\u0430;<\/p>\n<\/li>\n<li>\n<p>\u0434\u043b\u044f <code>fadeInDuration<\/code> \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <code>Duration.zero<\/code>.<\/p>\n<\/li>\n<li>\n<p>\u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0432\u0441\u0451 \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435 <strong>Hero<\/strong>, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f <strong>CustomRectTween<\/strong> \u0434\u043b\u044f \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430 <code>createRectTween<\/code>.<\/p>\n<\/li>\n<li>\n<p>\u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0432\u0441\u0435 \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435 <strong>InteractiveViewer<\/strong> \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u043b\u0438 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0442\u044c \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044e.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u044b\u0448\u0435\u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u0434 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043e\u0442 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u043e\u0433\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043a \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044e \u0432 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u043c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0438 (\u0435\u0441\u043b\u0438 \u043e\u043d\u043e \u0443\u0436\u0435 \u0431\u044b\u043b\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e), \u043d\u043e \u0438 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u044d\u0442\u043e\u0442 \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u0432 hero \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e! ?<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u0437\u0430\u0439\u0434\u0435\u043c \u0432 \u0432\u0438\u0434\u0436\u0435\u0442 <strong>ProfilePage <\/strong>\u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043c \u043a\u043e\u0434 \u0434\u043b\u044f <strong>SliverChildBuilderDelegate<\/strong> \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0432\u0438\u0434\u0443:<\/p>\n<pre><code class=\"dart\">...  delegate: SliverChildBuilderDelegate(   (context, index) {     final activity = activities[index];     final url =         activity.extraData!['resized_image_url'] as String;     return GestureDetector(       onTap: () {         Navigator.of(context).push(           HeroDialogRoute(             builder: (context) {               return _PictureViewer(activity: activity);             },           ),         );       },       child: Hero(         tag: 'hero-image-${activity.id}',         child: CachedNetworkImage(           key: ValueKey('image-${activity.id}'),           width: 200,           height: 200,           fit: BoxFit.cover,           imageUrl: url,         ),       ),     );   },   childCount: activities.length, ),  ...<\/code><\/pre>\n<p>\u0412 \u0432\u044b\u0448\u0435\u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u043c \u043a\u043e\u0434\u0435 \u043c\u044b \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c <strong>CashedNewtworkImage<\/strong> \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435 <strong>Hero<\/strong> \u0438 \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435 <strong>GestureDetector<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043d\u0435\u0434\u0430\u0432\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0433\u043e\u00a0 <strong>HeroDialogRoute<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0441\u0432\u043e\u044e \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0432\u0438\u0434\u0436\u0435\u0442 <strong>_PictureViewer<\/strong>.<\/p>\n<blockquote>\n<p>\u26a0\ufe0f \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u044b \u0432\u0435\u0437\u0434\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u043e\u0434\u043d\u0438 \u0438 \u0442\u0435 \u0436\u0435<strong> Hero<\/strong> \u0442\u044d\u0433\u0438, \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430\u0445 \u043d\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u043b\u043e \u043e\u0448\u0438\u0431\u043e\u043a. \u0422\u0430\u043a\u0436\u0435 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u044b \u043d\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u0443\u0435\u0442\u0435 \u043e\u0434\u043d\u0438 \u0438 \u0442\u0435 \u0436\u0435 \u0442\u044d\u0433\u0438 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u044d\u043a\u0440\u0430\u043d\u0435 &#8212; \u043f\u0440\u0438\u0447\u0438\u043d\u0430 \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <code>activity.id<\/code>.<\/p>\n<\/blockquote>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u0443\u044e \u043f\u043b\u0430\u0432\u043d\u0443\u044e \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u043a\u0430\u043a \u043d\u0430 \u0432\u0438\u0434\u0435\u043e \u043d\u0438\u0436\u0435:<\/p>\n<div class=\"tm-iframe_temp\" data-src=\"https:\/\/embedd.srv.habr.com\/iframe\/6456cab9a32dad00c4bdb462\" data-style=\"\" id=\"6456cab9a32dad00c4bdb462\" width=\"\"><\/div>\n<h2>\u0418\u0437\u043c\u0435\u043d\u044f\u0435\u043c \u0432\u0435\u0440\u0445\u043d\u044e\u044e \u043f\u0430\u043d\u0435\u043b\u044c<\/h2>\n<p>\u041d\u0430 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0430\u0448\u0430 \u0432\u0435\u0440\u0445\u043d\u044f\u044f \u043f\u0430\u043d\u0435\u043b\u044c (<strong>AppBar<\/strong>) \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u0443\u0441\u0442\u043e\u0439. \u0412\u044b \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435 \u0442\u0430\u043a\u0436\u0435 \u0437\u0430\u043c\u0435\u0442\u0438\u043b\u0438, \u0447\u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u043d\u0435\u0442 \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u043f\u043e\u0441\u0442 \u0432 \u043d\u0430\u0448 \u043f\u0440\u043e\u0444\u0438\u043b\u044c, \u0435\u0441\u043b\u0438 \u043e\u043d \u0443\u0436\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u0434\u0438\u043d.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u044d\u0442\u043e.<\/p>\n<p>\u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 <code>components\/home\/home_screen.dart<\/code> \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 <strong>AppBar<\/strong> \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430 <code>actions<\/code>:<\/p>\n<pre><code class=\"dart\">...  import '..\/app_widgets\/app_widgets.dart'; import '..\/new_post\/new_post.dart';  ...  AppBar(  ...  actions: [     Padding(       padding: const EdgeInsets.all(8),       child: TapFadeIcon(         onTap: () => Navigator.of(context).push(NewPostScreen.route),         icon: Icons.add_circle_outline,         iconColor: iconColor,       ),     ),     Padding(       padding: const EdgeInsets.all(8),       child: TapFadeIcon(         onTap: () async {           context.removeAndShowSnackbar('Not part of the demo');         },         icon: Icons.favorite_outline,         iconColor: iconColor,       ),     ),     Padding(       padding: const EdgeInsets.all(8.0),       child: TapFadeIcon(         onTap: () => context.removeAndShowSnackbar('Not part of the demo'),         icon: Icons.call_made,         iconColor: iconColor,       ),     ),   ],  ...<\/code><\/pre>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u043a\u043e\u0434\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043a\u043b\u0430\u0441\u0441 <strong>TapFadeIcon,<\/strong> \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0440\u0430\u043d\u0435\u0435, \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438\u043a\u043e\u043d\u043a\u0438 \u043d\u0430 \u043d\u0430\u0448\u0435\u0439 \u0432\u0435\u0440\u0445\u043d\u0435\u0439 \u043f\u0430\u043d\u0435\u043b\u0438. \u0414\u0432\u0435 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0438\u043a\u043e\u043d\u043e\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043b\u0438\u0448\u044c \u0432 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u0446\u0435\u043b\u044f\u0445 \u0438 \u043d\u0435 \u043d\u0435\u0441\u0443\u0442 \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430. \u041e\u0434\u043d\u0430\u043a\u043e \u0441\u0430\u043c\u0430\u044f \u043f\u0435\u0440\u0432\u0430\u044f \u0438\u043a\u043e\u043d\u043a\u0430 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043a\u043b\u0430\u0441\u0441 <strong>NewPostScreen<\/strong> (\u042d\u043a\u0440\u0430\u043d &#8212; \u00ab\u041d\u043e\u0432\u0430\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f\u00bb) \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043d\u0430 \u043d\u0435\u0435.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043f\u043e\u0441\u0442\u043e\u0432 \u0432 \u0432\u0430\u0448 \u043f\u0440\u043e\u0444\u0438\u043b\u044c, \u0430 \u0432\u0430\u0448\u0430 \u0432\u0435\u0440\u0445\u043d\u044f\u044f \u043f\u0430\u043d\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u043d\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9e6\/ade\/5a0\/9e6ade5a05718f1aa1264b90eb1654a0.png\" width=\"293\" height=\"633\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9e6\/ade\/5a0\/9e6ade5a05718f1aa1264b90eb1654a0.png\"\/><\/figure>\n<h2>\u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a\u0438<\/h2>\n<p>\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0438 \u043e\u0442\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0439 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u044e Instagram. \u0411\u0435\u0437 \u043d\u0435\u0435, \u043c\u044b \u0431\u044b \u0441\u0442\u0430\u0432\u0438\u043b\u0438 \u043b\u0430\u0439\u043a\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0430\u043c\u0438\u043c \u0441\u0435\u0431\u0435. ?<\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u043f\u043e\u0438\u0441\u043a\u0430<\/h2>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b<strong> <\/strong><code>components\/search\/search_page.dart<\/code><strong> <\/strong>\u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u043d\u0435\u0433\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434:<\/p>\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:stream_feed_flutter_core\/stream_feed_flutter_core.dart';  import '..\/..\/app\/app.dart'; import '..\/app_widgets\/app_widgets.dart';  \/\/\/ Page to find other users and follow\/unfollow. class SearchPage extends StatelessWidget {   \/\/\/ Create a new [SearchPage].   const SearchPage({Key? key}) : super(key: key);    @override   Widget build(BuildContext context) {     final users = List&lt;DemoAppUser>.from(DemoAppUser.values)       ..removeWhere((it) => it.id == context.appState.user.id);     return ListView.builder(       itemCount: users.length,       itemBuilder: (context, index) {         return _UserProfile(userId: users[index].id!);       },     );   } }  class _UserProfile extends StatefulWidget {   const _UserProfile({     Key? key,     required this.userId,   }) : super(key: key);    final String userId;    @override   __UserProfileState createState() => __UserProfileState(); }  class __UserProfileState extends State&lt;_UserProfile> {   late StreamUser streamUser;   late bool isFollowing;   late Future&lt;StreamagramUser> userDataFuture = getUser();    Future&lt;StreamagramUser> getUser() async {     final userClient = context.appState.client.user(widget.userId);     final futures = await Future.wait([       userClient.get(),       _isFollowingUser(widget.userId),     ]);     streamUser = futures[0] as StreamUser;     isFollowing = futures[1] as bool;      return StreamagramUser.fromMap(streamUser.data!);   }    \/\/\/ Determine if the current authenticated user is following [user].   Future&lt;bool> _isFollowingUser(String userId) async {     return FeedProvider.of(context).bloc.isFollowingFeed(followerId: userId);   }    @override   Widget build(BuildContext context) {     return FutureBuilder&lt;StreamagramUser>(       future: userDataFuture,       builder: (context, snapshot) {         switch (snapshot.connectionState) {           case ConnectionState.waiting:             return const SizedBox.shrink();           default:             if (snapshot.hasError) {               return const Padding(                 padding: EdgeInsets.all(8.0),                 child: Text('Could not load profile'),               );             } else {               final userData = snapshot.data;               if (userData != null) {                 return _ProfileTile(                   user: streamUser,                   userData: userData,                   isFollowing: isFollowing,                 );               }               return const SizedBox.shrink();             }         }       },     );   } }  class _ProfileTile extends StatefulWidget {   const _ProfileTile({     Key? key,     required this.user,     required this.userData,     required this.isFollowing,   }) : super(key: key);    final StreamUser user;   final StreamagramUser userData;   final bool isFollowing;    @override   __ProfileTileState createState() => __ProfileTileState(); }  class __ProfileTileState extends State&lt;_ProfileTile> {   bool _isLoading = false;   late bool _isFollowing = widget.isFollowing;    Future&lt;void> followOrUnfollowUser(BuildContext context) async {     setState(() {       _isLoading = true;     });     if (_isFollowing) {       final bloc = FeedProvider.of(context).bloc;       await bloc.unfollowFeed(unfolloweeId: widget.user.id);       _isFollowing = false;     } else {       await FeedProvider.of(context)           .bloc           .followFeed(followeeId: widget.user.id);       _isFollowing = true;     }     FeedProvider.of(context)         .bloc         .queryEnrichedActivities(           feedGroup: 'timeline',           flags: EnrichmentFlags()             ..withOwnReactions()             ..withRecentReactions()             ..withReactionCounts(),         );      setState(() {       _isLoading = false;     });   }    @override   Widget build(BuildContext context) {     return Row(       children: [         Padding(           padding: const EdgeInsets.all(8.0),           child: Avatar.medium(streamagramUser: widget.userData),         ),         Padding(           padding: const EdgeInsets.all(8.0),           child: Column(             crossAxisAlignment: CrossAxisAlignment.start,             children: [               Text(widget.user.id, style: AppTextStyle.textStyleBold),               Text(                 widget.userData.fullName,                 style: AppTextStyle.textStyleFaded,               ),             ],           ),         ),         const Spacer(),         Padding(           padding: const EdgeInsets.symmetric(horizontal: 8.0),           child: _isLoading               ? const CircularProgressIndicator(strokeWidth: 3)               : OutlinedButton(                   onPressed: () {                     followOrUnfollowUser(context);                   },                   child: _isFollowing                       ? const Text('Unfollow')                       : const Text('Follow'),                 ),         )       ],     );   } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043d\u0435\u043c\u0430\u043b\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u0430\u043c \u043b\u0443\u0447\u0448\u0435 \u0438\u0437\u0443\u0447\u0438\u0442\u044c \u044d\u0442\u0443 \u0447\u0430\u0441\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0432 \u0432\u0430\u0448\u0435\u043c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u043c \u0442\u0435\u043c\u043f\u0435. \u0415\u0441\u043b\u0438 \u043a\u043e\u0440\u043e\u0442\u043a\u043e, \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u043a\u043e\u0434\u0435 \u043c\u044b:<\/p>\n<ul>\n<li>\n<p>\u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0432\u0441\u0435\u0445 \u043d\u0430\u0448\u0438\u0445 \u0444\u0435\u0439\u043a\u043e\u0432\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u0443\u0431\u0438\u0440\u0430\u0435\u043c \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u043e\u0434\u043d\u043e\u0433\u043e, \u043f\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043c\u044b \u0437\u0430\u0448\u043b\u0438 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435;<\/p>\n<\/li>\n<li>\n<p>\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c \u0432\u0441\u0435\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f <strong>_UserProfile<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 <code>getUser<\/code> \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435 <strong>_UserProfile<\/strong>. \u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d \u043b\u0438 \u0443\u0436\u0435 \u044d\u0442\u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 <code>isFollowingFeed<\/code> \u0432 <strong>FeedBloc<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0432\u0438\u0434\u0436\u0435\u0442 <strong>FutureBuilder<\/strong> \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0434\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u043e\u0442 \u043c\u0435\u0442\u043e\u0434\u0430 <code>getUser<\/code>.<\/p>\n<\/li>\n<li>\n<p>\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0432\u0438\u0434\u0436\u0435\u0442 <strong>_ProfileTile<\/strong> \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0438 \u0434\u0435\u043b\u0430\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u043c \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0438 \u043e\u0442\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 <code>followOrUnfollowUser<\/code>. <strong>FeedBloc<\/strong>, \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0438 \u043e\u0442\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0438\u0445 ID;<\/p>\n<\/li>\n<li>\n<p>\u043c\u0435\u0442\u043e\u0434\u044b <code>followFeed<\/code> \u0438 <code>unfollowFeed<\/code>\u00a0 \u0432 <strong>FeedBloc<\/strong> \u0438\u043c\u0435\u044e\u0442 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e (<strong>user<\/strong>) \u0438 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e (<strong>timeline<\/strong>) \u043b\u0435\u043d\u0442\u044b. \u0415\u0441\u043b\u0438 \u0432\u044b \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0437\u0430\u0434\u0430\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u0430\u0448\u0438\u0445 \u043b\u0435\u043d\u0442, \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u044d\u0442\u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u043d\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435.<\/p>\n<\/li>\n<\/ul>\n<p>\u041c\u0435\u0442\u043e\u0434 <code>followOrUnfollowUser<\/code> \u0442\u0430\u043a\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code class=\"dart\">FeedProvider.of(context).bloc.queryEnrichedActivities(           feedGroup: 'timeline',           flags: EnrichmentFlags()             ..withOwnReactions()             ..withRecentReactions()             ..withReactionCounts(),         );<\/code><\/pre>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043a\u043e\u0434\u0430 \u043c\u044b \u0444\u043e\u0440\u0441\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043b\u0435\u043d\u0442\u044b (<strong>timeline feed<\/strong>). \u0418\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u043c \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u043c \u0437\u0434\u0435\u0441\u044c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 <strong>Flags<\/strong>.<strong> <\/strong>\u041e\u043d \u0434\u0430\u0435\u0442 \u0441\u0438\u0433\u043d\u0430\u043b API \u043e \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u043f\u043e\u0441\u0442\u0430\u0445 \u0432 \u043b\u0435\u043d\u0442\u0435 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u044d\u0442\u043e\u0433\u043e \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430. \u041f\u0440\u043e\u0449\u0435 \u0433\u043e\u0432\u043e\u0440\u044f, \u043e\u043d \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043e \u043f\u043e\u0441\u0442\u0430\u0445 \u0438 \u043e \u0440\u0435\u0430\u043a\u0446\u0438\u044f\u0445 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u043d\u0430 \u043d\u0438\u0445. \u041f\u043e\u0437\u0436\u0435 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0435\u0449\u0435 \u0432\u0435\u0440\u043d\u0435\u043c\u0441\u044f \u043a \u044d\u0442\u043e\u043c\u0443.<\/p>\n<p>\u0414\u0430\u043b\u0435\u0435, \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b <code>components\/search\/search.dart<\/code> \u0438 \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u0435\u0433\u043e:<\/p>\n<pre><code class=\"dart\">export 'search_page.dart';<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0441\u043f\u043e\u0441\u043e\u0431 \u043a\u0430\u043a \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 \u043d\u0430\u0448\u0443 \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u043f\u043e\u0438\u0441\u043a\u0430. \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 <code>components\/home\/home_screen.dart<\/code> \u0438 \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435 <strong>HomeScreen<\/strong> \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e <code>_homePages<\/code> \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"dart\">... import 'package:stream_agram\/components\/search\/search.dart'; ... \/\/\/ List of pages available from the home screen. static const List&lt;Widget> _homePages = &lt;Widget>[   Center(child: Text('TimelinePage')),   SearchPage(), \/\/ ADD THIS   ProfilePage(), ]; ...<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0432\u0441\u0435 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u0442\u0435\u043f\u0435\u0440\u044c \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u043d\u0430 <strong>\u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u043f\u043e\u0438\u0441\u043a\u0430<\/strong>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/2d0\/044\/d48\/2d0044d48efb3fee7d53a3a5a923754d.png\" width=\"293\" height=\"633\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2d0\/044\/d48\/2d0044d48efb3fee7d53a3a5a923754d.png\"\/><\/figure>\n<blockquote>\n<p>\u26a0\ufe0f <strong>\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/strong>: \u0435\u0441\u043b\u0438 \u0432\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0435 \u043e\u0448\u0438\u0431\u043a\u0443 \u0441 \u0442\u0435\u043a\u0441\u0442\u043e\u043c \u00abCould not load profile\u00bb, \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u044b \u0435\u0449\u0435 \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0430\u043a\u043a\u0430\u0443\u043d\u0442. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043f\u043e\u0434 \u043a\u0430\u0436\u0434\u044b\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c \u0445\u043e\u0442\u044f \u0431\u044b \u043e\u0434\u0438\u043d \u0440\u0430\u0437! <\/p>\n<\/blockquote>\n<h2>\u0412\u043e\u0441\u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443<\/h2>\n<p>\u0421\u0435\u0439\u0447\u0430\u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043d\u0430\u0448\u0435\u0433\u043e Stream-agram \u043c\u043e\u0433\u0443\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u0441\u0442\u044b \u0432 \u0441\u0432\u043e\u044e <strong>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443<\/strong>, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0438 \u043e\u0442\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0442\u0430\u043a\u0438\u043c \u0436\u0435 \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043a\u0430\u043a \u044d\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u043c Instagram. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 &#8212; \u043f\u0440\u0438\u0448\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u0432\u0437\u044f\u0442\u044c\u0441\u044f \u0437\u0430 <strong>\u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443<\/strong>.<\/p>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c UI \u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0438 \u0445\u043e\u0442\u044f \u0431\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 &#8212; \u043d\u0435 \u0442\u0430\u043a\u0430\u044f \u0443\u0436 \u0438 \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430. \u0412\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0441\u0440\u0430\u0437\u0443 \u0443\u0433\u043b\u0443\u0431\u043b\u044f\u0442\u044c\u0441\u044f \u0432 \u044d\u0442\u0438 \u0434\u0435\u0431\u0440\u0438, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e UI \u0432\u0438\u0434\u0436\u0435\u0442\u043e\u0432, \u043e\u0442 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043e\u043f\u0438\u0440\u0430\u0442\u044c\u0441\u044f.<\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 \u043b\u0430\u0439\u043a\u0430 \u0438 \u043f\u043e\u043b\u0435 \u00ab\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439\u00bb<\/h2>\n<p>\u041c\u043e\u0436\u0435\u0442\u0435 \u043b\u0438 \u0432\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0446. \u0441\u0435\u0442\u044c \u0442\u0430\u043a\u043e\u0435 \u043a\u0430\u043a Instagram \u0431\u0435\u0437 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043b\u0430\u0439\u043a\u0438 \u0438 \u0440\u0435\u0430\u043a\u0446\u0438\u0438? \u0414\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u0432\u043d\u0435\u0434\u0440\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0432 \u043d\u0430\u0448 \u043a\u043b\u043e\u043d, \u0432\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0432\u0438\u0434\u0436\u0435\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043b\u0430\u0439\u043a\u043e\u0432, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0432\u0438\u0434\u0436\u0435\u0442 \u0434\u043b\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043a \u043f\u043e\u0441\u0442\u0443 (\u0432\u0438\u0434\u0436\u0435\u0442 &#8212; <strong>TextField<\/strong>).<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0447\u043d\u0435\u043c \u0441 \u043a\u043d\u043e\u043f\u043a\u0438 \u043b\u0430\u0439\u043a\u0430\/\u0441\u0435\u0440\u0434\u0435\u0447\u043a\u0430.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b <code>components\/app_widgets\/favorite_icon.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u043d\u0435\u0433\u043e:<\/p>\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:stream_agram\/app\/theme.dart';  \/\/\/ {@template favorite_icon_button} \/\/\/ Animated button to indicate if a post\/comment is liked. \/\/\/ \/\/\/ Pass in onPressed to \/\/\/ {@endtemplate} class FavoriteIconButton extends StatefulWidget {   \/\/\/ {@macro favorite_icon_button}   const FavoriteIconButton({     Key? key,     required this.isLiked,     this.size = 22,     required this.onTap,   }) : super(key: key);    \/\/\/ Indicates if it is liked or not.   final bool isLiked;    \/\/\/ Size of the icon.   final double size;    \/\/\/ onTap callback. Returns a value to indicate if liked or not.   final Function(bool val) onTap;    @override   _FavoriteIconButtonState createState() => _FavoriteIconButtonState(); }  class _FavoriteIconButtonState extends State&lt;FavoriteIconButton> {   late bool isLiked = widget.isLiked;    void _handleTap() {     setState(() {       isLiked = !isLiked;     });     widget.onTap(isLiked);   }    @override   Widget build(BuildContext context) {     return GestureDetector(       onTap: _handleTap,       child: AnimatedCrossFade(         firstCurve: Curves.easeIn,         secondCurve: Curves.easeOut,         firstChild: Icon(           Icons.favorite,           color: AppColors.like,           size: widget.size,         ),         secondChild: Icon(           Icons.favorite_outline,           size: widget.size,         ),         crossFadeState:             isLiked ? CrossFadeState.showFirst : CrossFadeState.showSecond,         duration: const Duration(milliseconds: 200),       ),     );   } }<\/code><\/pre>\n<p>\u042d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0432\u0438\u0434\u0436\u0435\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0438\u043a\u043e\u043d\u043a\u0443 \u0441\u0435\u0440\u0434\u0435\u0447\u043a\u0430, \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043d\u0430 \u043d\u0435\u0435. \u041e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 <strong>onTap<\/strong>. \u042d\u0442\u043e\u0442 \u0432\u0438\u0434\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u043a\u0442\u043e-\u0442\u043e \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u043b \u043b\u0430\u0439\u043a \u043d\u0430 \u043f\u043e\u0441\u0442 \u0438\u043b\u0438 \u043f\u0440\u043e\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043b \u0435\u0433\u043e.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/571\/4e0\/b57\/5714e0b5754942a24f0a57771dd4c534.png\" width=\"366\" height=\"128\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/571\/4e0\/b57\/5714e0b5754942a24f0a57771dd4c534.png\"\/><\/figure>\n<p>\u0414\u0430\u043b\u0435\u0435, \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0432\u0438\u0434\u0436\u0435\u0442 <strong>CommentBox <\/strong>(\u0434\u043b\u044f \u043f\u043e\u043b\u044f \u00ab\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439\u00bb).<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b <code>components\/app_widgets\/comment_box.dart<\/code>\u00a0 \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u043d\u0435\u0433\u043e:<\/p>\n<pre><code class=\"dart\">import 'package:flutter\/material.dart';  import '..\/..\/app\/app.dart'; import 'app_widgets.dart';  \/\/\/ Displays a text field styled to easily add comments to posts. \/\/\/ \/\/\/ Quickly add emoji reactions. class CommentBox extends StatelessWidget {   \/\/\/ Creates a [CommentBox].   const CommentBox({     Key? key,     required this.commenter,     required this.textEditingController,     required this.focusNode,     required this.onSubmitted,   }) : super(key: key);    final StreamagramUser commenter;   final TextEditingController textEditingController;   final FocusNode focusNode;   final Function(String?) onSubmitted;    @override   Widget build(BuildContext context) {     final border = _border(context);     return Container(       decoration: BoxDecoration(         color: (Theme.of(context).brightness == Brightness.light)             ? AppColors.light             : AppColors.dark,         border: Border(             top: BorderSide(           color: (Theme.of(context).brightness == Brightness.light)               ? AppColors.ligthGrey               : AppColors.grey,         )),       ),       child: Column(         mainAxisSize: MainAxisSize.min,         children: [           Padding(             padding: const EdgeInsets.symmetric(vertical: 8.0),             child: Row(               mainAxisAlignment: MainAxisAlignment.spaceAround,               children: [                 _emojiText('\u2764\ufe0f'),                 _emojiText('?'),                 _emojiText('?'),                 _emojiText('??'),                 _emojiText('?'),                 _emojiText('?'),                 _emojiText('?'),                 _emojiText('?'),               ],             ),           ),           Row(             children: [               Padding(                 padding: const EdgeInsets.all(8.0),                 child: Avatar.medium(streamagramUser: commenter),               ),               Expanded(                 child: TextField(                   controller: textEditingController,                   focusNode: focusNode,                   onSubmitted: onSubmitted,                   minLines: 1,                   maxLines: 10,                   style: const TextStyle(fontSize: 14),                   decoration: InputDecoration(                       suffix: _DoneButton(                         textEditorFocusNode: focusNode,                         textEditingController: textEditingController,                         onSubmitted: onSubmitted,                       ),                       hintText: 'Add a comment...',                       isDense: true,                       contentPadding: const EdgeInsets.symmetric(                           horizontal: 16, vertical: 12),                       focusedBorder: border,                       border: border,                       enabledBorder: border),                 ),               ),               const SizedBox(                 width: 8,               ),             ],           ),         ],       ),     );   }    OutlineInputBorder _border(BuildContext context) {     return OutlineInputBorder(       borderRadius: const BorderRadius.all(Radius.circular(24)),       borderSide: BorderSide(         color: (Theme.of(context).brightness == Brightness.light)             ? AppColors.grey.withOpacity(0.3)             : AppColors.light.withOpacity(0.5),         width: 0.5,       ),     );   }    Widget _emojiText(String emoji) {     return GestureDetector(       onTap: () {         focusNode.requestFocus();         textEditingController.text = textEditingController.text + emoji;         textEditingController.selection = TextSelection.fromPosition(             TextPosition(offset: textEditingController.text.length));       },       child: Text(         emoji,         style: const TextStyle(fontSize: 24),       ),     );   } }  class _DoneButton extends StatefulWidget {   const _DoneButton({     Key? key,     required this.onSubmitted,     required this.textEditorFocusNode,     required this.textEditingController,   }) : super(key: key);    final Function(String?) onSubmitted;   final FocusNode textEditorFocusNode;   final TextEditingController textEditingController;    @override   State&lt;_DoneButton> createState() => _DoneButtonState(); }  class _DoneButtonState extends State&lt;_DoneButton> {   final fadedTextStyle =       AppTextStyle.textStyleAction.copyWith(color: Colors.grey);   late TextStyle textStyle = fadedTextStyle;    @override   void initState() {     super.initState();     widget.textEditingController.addListener(() {       if (widget.textEditingController.text.isNotEmpty) {         textStyle = AppTextStyle.textStyleAction;       } else {         textStyle = fadedTextStyle;       } if (mounted) {       setState(() {}); }     });   }    @override   Widget build(BuildContext context) {     return widget.textEditorFocusNode.hasFocus         ? GestureDetector(             onTap: () {               widget.onSubmitted(widget.textEditingController.text);             },             child: Text(               'Done',               style: textStyle,             ),           )         : const SizedBox.shrink();   } }<\/code><\/pre>\n<p>\u0412\u043a\u0440\u0430\u0442\u0446\u0435, \u044d\u0442\u043e\u0442 \u0432\u0438\u0434\u0436\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<ul>\n<li>\n<p>\u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u043a\u043b\u0430\u0441\u0441 <strong>StreamagramUser<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0442\u043e\u0440\u0430 (<code>commenter<\/code>) \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u0444\u043e\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0422\u0430\u043a\u0436\u0435 \u044d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f <strong>FocusNode<\/strong>, <strong>TextEditingController <\/strong>\u0438 \u043c\u0435\u0442\u043e\u0434 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 <code>onSubmitted<\/code>, \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430. \u041c\u0435\u0442\u043e\u0434 <code>onSubmitted<\/code> \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u043d \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e (\u043f\u043e\u0441\u043b\u0435 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043a\u043d\u043e\u043f\u043a\u0438 <strong>Done <\/strong>\u0438\u043b\u0438<strong> return<\/strong>);<\/p>\n<\/li>\n<li>\n<p>\u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0438 \u0441\u0442\u0438\u043b\u0435\u0439. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043a\u043d\u043e\u043f\u043a\u0443 \u00ab<strong>Done<\/strong>\u00bb (\u0413\u043e\u0442\u043e\u0432\u043e);<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u0443\u044e \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044e \u043f\u0430\u043d\u0435\u043b\u044c \u0441 \u044d\u043c\u043e\u0434\u0437\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 <strong>TextEditingController<\/strong> \u043f\u043e\u0441\u043b\u0435 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043d\u0430 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445.<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/bdb\/bbb\/a7e\/bdbbbba7e4ac3baa9edf47d39eb37a89.png\" width=\"852\" height=\"212\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/bdb\/bbb\/a7e\/bdbbbba7e4ac3baa9edf47d39eb37a89.png\"\/><\/figure>\n<p>\u041d\u0430\u043a\u043e\u043d\u0435\u0446, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u043c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b\u0443 <code>components\/app_widgets\/app_widgets.dart<\/code>.<\/p>\n<pre><code class=\"dart\">export 'avatars.dart'; export 'tap_fade_icon.dart'; export 'favorite_icon.dart'; \/\/ ADD THIS export 'comment_box.dart'; \/\/ ADD THIS<\/code><\/pre>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0438\u0434\u0436\u0435\u0442 PostCard<\/h2>\n<p>\u041f\u0440\u0438\u0433\u043e\u0442\u043e\u0432\u044c\u0442\u0435\u0441\u044c, \u0441\u0435\u0439\u0447\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u043c\u043d\u043e\u0433\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438. \u042d\u0442\u043e\u0442 \u0432\u0438\u0434\u0436\u0435\u0442 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0432\u0441\u0451, \u0447\u0442\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u0441 \u0435\u0434\u0438\u043d\u0438\u0447\u043d\u044b\u043c \u043f\u043e\u0441\u0442\u043e\u043c.<\/p>\n<p>\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u0432\u0438\u0434\u0436\u0435\u0442\u0430 \u0437\u0430\u0439\u043c\u0435\u0442 \u0443\u0439\u043c\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e, \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u0437\u0433\u043b\u044f\u043d\u0443\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443. ?<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/034\/950\/32b\/03495032baa8f6af59484f1d6ff4c51a.png\" width=\"406\" height=\"685\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/034\/950\/32b\/03495032baa8f6af59484f1d6ff4c51a.png\"\/><\/figure>\n<p>\u0412 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0432\u0441\u0435\u0433\u043e, \u0447\u0442\u043e \u0432\u044b \u0432\u0438\u0434\u0438\u0442\u0435 \u0432\u044b\u0448\u0435, \u0442\u0430\u043a \u0438\u043b\u0438 \u0438\u043d\u0430\u0447\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u043d \u0432\u0438\u0434\u0436\u0435\u0442 <strong>PostCard<\/strong>. \u041c\u044b \u0438\u043c\u0435\u0435\u043c \u0432\u0432\u0438\u0434\u0443:<\/p>\n<ul>\n<li>\n<p>\u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 \u043a \u043f\u043e\u0441\u0442\u0443;<\/p>\n<\/li>\n<li>\n<p>\u0441\u0430\u043c\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 (\u043f\u043e\u0441\u0442);<\/p>\n<\/li>\n<li>\n<p>\u043a\u043d\u043e\u043f\u043a\u0443 \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043b\u0430\u0439\u043a \u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 (\u043a\u043d\u043e\u043f\u043a\u0438 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u0437\u0430\u043a\u043b\u0430\u0434\u043a\u0438 &#8212; \u0432\u0441\u0435\u0433\u043e \u043b\u0438\u0448\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u043d\u0435 \u043d\u0435\u0441\u0443\u0449\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430);<\/p>\n<\/li>\n<li>\n<p>\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043a \u0444\u043e\u0442\u043e;<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u043b\u0435 \u00ab\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439\u00bb (Add a comment).<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f\u00a0 \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b <code>components\/timeline\/widgets\/post_card.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434:<\/p>\n<pre><code class=\"dart\">import 'package:cached_network_image\/cached_network_image.dart'; import 'package:flutter\/material.dart'; import 'package:jiffy\/jiffy.dart'; import 'package:provider\/provider.dart'; import 'package:stream_feed_flutter_core\/stream_feed_flutter_core.dart';  import '..\/..\/..\/app\/app.dart'; import '..\/..\/app_widgets\/app_widgets.dart';  typedef OnAddComment = void Function(   EnrichedActivity activity, {   String? message, });  \/\/\/ {@template post_card} \/\/\/ A card that displays a user post\/activity. \/\/\/ {@endtemplate} class PostCard extends StatelessWidget {   \/\/\/ {@macro post_card}   const PostCard({     Key? key,     required this.enrichedActivity,     required this.onAddComment,   }) : super(key: key);    \/\/\/ Enriched activity (post) to display.   final EnrichedActivity enrichedActivity;   final OnAddComment onAddComment;    @override   Widget build(BuildContext context) {     final actorData = enrichedActivity.actor!.data;     final userData = StreamagramUser.fromMap(actorData as Map&lt;String, dynamic>);      return Column(       crossAxisAlignment: CrossAxisAlignment.start,       children: [         _ProfileSlab(           userData: userData,         ),         _PictureCarousal(           enrichedActivity: enrichedActivity,         ),         _Description(           enrichedActivity: enrichedActivity,         ),         _InteractiveCommentSlab(           enrichedActivity: enrichedActivity,           onAddComment: onAddComment,         ),       ],     );   } }  class _PictureCarousal extends StatefulWidget {   const _PictureCarousal({     Key? key,     required this.enrichedActivity,   }) : super(key: key);    final EnrichedActivity enrichedActivity;    @override   __PictureCarousalState createState() => __PictureCarousalState(); }  class __PictureCarousalState extends State&lt;_PictureCarousal> {   late var likeReactions = getLikeReactions() ?? [];   late var likeCount = getLikeCount() ?? 0;    Reaction? latestLikeReaction;    List&lt;Reaction>? getLikeReactions() {     return widget.enrichedActivity.latestReactions?['like'] ?? [];   }    int? getLikeCount() {     return widget.enrichedActivity.reactionCounts?['like'] ?? 0;   }    Future&lt;void> _addLikeReaction() async {     latestLikeReaction = await context.appState.client.reactions.add(       'like',       widget.enrichedActivity.id!,       userId: context.appState.user.id,     );      setState(() {       likeReactions.add(latestLikeReaction!);       likeCount++;     });   }    Future&lt;void> _removeLikeReaction() async {     late String? reactionId;     \/\/ A new reaction was added to this state.     if (latestLikeReaction != null) {       reactionId = latestLikeReaction?.id;     } else {       \/\/ An old reaction has been retrieved from Stream.       final prevReaction = widget.enrichedActivity.ownReactions?['like'];       if (prevReaction != null &amp;&amp; prevReaction.isNotEmpty) {         reactionId = prevReaction[0].id;       }     }      try {       if (reactionId != null) {         await context.appState.client.reactions.delete(reactionId);       }     } catch (e) {       debugPrint(e.toString());     }     setState(() {       likeReactions.removeWhere((element) => element.id == reactionId);       likeCount--;       latestLikeReaction = null;     });   }    @override   Widget build(BuildContext context) {     return Column(       crossAxisAlignment: CrossAxisAlignment.start,       children: [         ..._pictureCarousel(context),         _likes(),       ],     );   }    \/\/\/ Picture carousal and interaction buttons.   List&lt;Widget> _pictureCarousel(BuildContext context) {     const iconPadding = EdgeInsets.symmetric(horizontal: 8, vertical: 4);     var imageUrl = widget.enrichedActivity.extraData!['image_url'] as String;     double aspectRatio =         widget.enrichedActivity.extraData!['aspect_ratio'] as double? ?? 1.0;     final iconColor = Theme.of(context).iconTheme.color!;     return [       Padding(         padding: const EdgeInsets.symmetric(vertical: 8.0),         child: Center(           child: ConstrainedBox(             constraints: const BoxConstraints(maxHeight: 500),             child: AspectRatio(               aspectRatio: aspectRatio,               child: CachedNetworkImage(                 imageUrl: imageUrl,               ),             ),           ),         ),       ),       Row(         children: [           const SizedBox(             width: 4,           ),           Padding(             padding: iconPadding,             child: FavoriteIconButton(               isLiked: widget.enrichedActivity.ownReactions?['like'] != null,               onTap: (liked) {                 if (liked) {                   _addLikeReaction();                 } else {                   _removeLikeReaction();                 }               },             ),           ),           Padding(             padding: iconPadding,             child: TapFadeIcon(               onTap: () {                 \/\/ TODO               },               icon: Icons.chat_bubble_outline,               iconColor: iconColor,             ),           ),           Padding(             padding: iconPadding,             child: TapFadeIcon(               onTap: () =>                   context.removeAndShowSnackbar('Message: Not yet implemented'),               icon: Icons.call_made,               iconColor: iconColor,             ),           ),           const Spacer(),           Padding(             padding: iconPadding,             child: TapFadeIcon(               onTap: () => context                   .removeAndShowSnackbar('Bookmark: Not yet implemented'),               icon: Icons.bookmark_border,               iconColor: iconColor,             ),           ),         ],       )     ];   }    Widget _likes() {     if (likeReactions.isNotEmpty) {       return Padding(         padding: const EdgeInsets.only(left: 16.0, top: 8),         child: Text.rich(           TextSpan(             text: 'Liked by ',             style: AppTextStyle.textStyleLight,             children: &lt;TextSpan>[               TextSpan(                   text: StreamagramUser.fromMap(                           likeReactions[0].user?.data as Map&lt;String, dynamic>)                       .fullName,                   style: AppTextStyle.textStyleBold),               if (likeCount > 1 &amp;&amp; likeCount &lt; 3) ...[                 const TextSpan(text: ' and '),                 TextSpan(                     text: StreamagramUser.fromMap(                             likeReactions[1].user?.data as Map&lt;String, dynamic>)                         .fullName,                     style: AppTextStyle.textStyleBold),               ],               if (likeCount > 3) ...[                 const TextSpan(text: ' and '),                 const TextSpan(                     text: 'others', style: AppTextStyle.textStyleBold),               ],             ],           ),         ),       );     } else {       return const SizedBox.shrink();     }   } }  class _Description extends StatelessWidget {   const _Description({     Key? key,     required this.enrichedActivity,   }) : super(key: key);    final EnrichedActivity enrichedActivity;    @override   Widget build(BuildContext context) {     return Padding(       padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 6),       child: Text.rich(         TextSpan(           children: &lt;TextSpan>[             TextSpan(                 text: enrichedActivity.actor!.id!,                 style: AppTextStyle.textStyleBold),             const TextSpan(text: ' '),             TextSpan(                 text: enrichedActivity.extraData?['description'] as String? ??                     ''),           ],         ),       ),     );   } }  class _InteractiveCommentSlab extends StatefulWidget {   const _InteractiveCommentSlab({     Key? key,     required this.enrichedActivity,     required this.onAddComment,   }) : super(key: key);    final EnrichedActivity enrichedActivity;   final OnAddComment onAddComment;    @override   _InteractiveCommentSlabState createState() => _InteractiveCommentSlabState(); }  class _InteractiveCommentSlabState extends State&lt;_InteractiveCommentSlab> {   EnrichedActivity get enrichedActivity => widget.enrichedActivity;    late final String _timeSinceMessage =       Jiffy(widget.enrichedActivity.time).fromNow();    List&lt;Reaction> get _commentReactions =>       enrichedActivity.latestReactions?['comment'] ?? [];    int get _commentCount => enrichedActivity.reactionCounts?['comment'] ?? 0;    @override   Widget build(BuildContext context) {     const textPadding = EdgeInsets.all(8);     const spacePadding = EdgeInsets.only(left: 20.0, top: 8);     final comments = _commentReactions;     final commentCount = _commentCount;     return Column(       crossAxisAlignment: CrossAxisAlignment.start,       children: [         if (commentCount > 0 &amp;&amp; comments.isNotEmpty)           Padding(             padding: spacePadding,             child: Text.rich(               TextSpan(                 children: &lt;TextSpan>[                   TextSpan(                       text: StreamagramUser.fromMap(                               comments[0].user?.data as Map&lt;String, dynamic>)                           .fullName,                       style: AppTextStyle.textStyleBold),                   const TextSpan(text: '  '),                   TextSpan(text: comments[0].data?['message'] as String?),                 ],               ),             ),           ),         if (commentCount > 1 &amp;&amp; comments.isNotEmpty)           Padding(             padding: spacePadding,             child: Text.rich(               TextSpan(                 children: &lt;TextSpan>[                   TextSpan(                       text: StreamagramUser.fromMap(                               comments[1].user?.data as Map&lt;String, dynamic>)                           .fullName,                       style: AppTextStyle.textStyleBold),                   const TextSpan(text: '  '),                   TextSpan(text: comments[1].data?['message'] as String?),                 ],               ),             ),           ),         if (commentCount > 2)           Padding(             padding: spacePadding,             child: GestureDetector(               onTap: () {                 \/\/ TODO               },               child: Text(                 'View all $commentCount comments',                 style: AppTextStyle.textStyleFaded,               ),             ),           ),         GestureDetector(           behavior: HitTestBehavior.opaque,           onTap: () {             widget.onAddComment(enrichedActivity);           },           child: Padding(             padding: const EdgeInsets.only(left: 16.0, top: 3, right: 8),             child: Row(               children: [                 const _ProfilePicture(),                 const Expanded(                   child: Padding(                     padding: EdgeInsets.only(left: 8.0),                     child: Text(                       'Add a comment',                       style: TextStyle(                         color: AppColors.faded,                         fontSize: 14,                       ),                     ),                   ),                 ),                 GestureDetector(                   onTap: () {                     widget.onAddComment(enrichedActivity, message: '\u2764\ufe0f');                   },                   child: const Padding(                     padding: textPadding,                     child: Text('\u2764\ufe0f'),                   ),                 ),                 GestureDetector(                   onTap: () {                     widget.onAddComment(enrichedActivity, message: '?');                   },                   child: const Padding(                     padding: textPadding,                     child: Text('?'),                   ),                 ),               ],             ),           ),         ),         Padding(           padding: const EdgeInsets.only(left: 16.0, top: 4),           child: Text(             _timeSinceMessage,             style: const TextStyle(               color: AppColors.faded,               fontWeight: FontWeight.w400,               fontSize: 13,             ),           ),         ),       ],     );   } }  class _ProfilePicture extends StatelessWidget {   const _ProfilePicture({Key? key}) : super(key: key);    @override   Widget build(BuildContext context) {     final streamagramUser = context.watch&lt;AppState>().streamagramUser;     if (streamagramUser == null) {       return const Icon(Icons.error);     }     return Avatar.small(       streamagramUser: streamagramUser,     );   } }  class _ProfileSlab extends StatelessWidget {   const _ProfileSlab({     Key? key,     required this.userData,   }) : super(key: key);    final StreamagramUser userData;    @override   Widget build(BuildContext context) {     return Padding(       padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 16.0),       child: Row(         children: [           Avatar.medium(streamagramUser: userData),           Padding(             padding: const EdgeInsets.all(8.0),             child: Text(               userData.fullName,               style: AppTextStyle.textStyleBold,             ),           ),           const Spacer(),           TapFadeIcon(             onTap: () => context.removeAndShowSnackbar('Not part of the demo'),             icon: Icons.more_horiz,             iconColor: Theme.of(context).iconTheme.color!,           ),         ],       ),     );   } }<\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u043c \u0444\u0430\u0439\u043b\u0435 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0432\u0441\u0435\u0433\u043e, \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043e\u0441\u0432\u0435\u0442\u0438\u0442\u044c. \u0421\u0430\u043c\u043e\u0435 \u0432\u0430\u0436\u043d\u043e\u0435, \u0447\u0442\u043e \u043c\u044b \u0437\u0434\u0435\u0441\u044c \u0434\u0435\u043b\u0430\u0435\u043c \u044d\u0442\u043e:<\/p>\n<ul>\n<li>\n<p>\u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c <strong>EnrichedActivity <\/strong>\u043a<strong> PostCard. <\/strong>\u041a\u043b\u0430\u0441\u0441 <strong>Enriched Activity<\/strong> \u0437\u0434\u0435\u0441\u044c \u043f\u043e \u0441\u0443\u0442\u0438 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c <strong>Activity<\/strong>, \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0441\u0442\u0435\u0440\u043e\u0438\u0434\u0430\u0445. \u041f\u0435\u0440\u0432\u044b\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 &#8212; \u0440\u0435\u0430\u043a\u0446\u0438\u0438). \u0420\u0435\u0430\u043a\u0446\u0438\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 \u0438\u043b\u0438 \u043b\u0430\u0439\u043a\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 <strong>OnAddComment<\/strong>. \u042d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u043a\u043d\u043e\u043f\u043a\u0430 \u00ab\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439\u00bb (\u043a \u043f\u043e\u0441\u0442\u0443).\u00a0 \u041c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0438 \u044d\u0442\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0432 \u043a\u043e\u0434, \u0447\u0442\u043e\u0431\u044b \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0436\u0438\u043c\u0430\u043b \u043d\u0430 \u044d\u043c\u043e\u0434\u0437\u0438, \u0441\u0438\u0433\u043d\u0430\u043b \u043e\u0431 \u044d\u0442\u043e\u043c \u0441\u0440\u0430\u0437\u0443 \u0436\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u043b\u0441\u044f \u0432\u0438\u0434\u0436\u0435\u0442\u0443 <strong>CommentBox<\/strong>. \u0418 \u0442\u0435\u043f\u0435\u0440\u044c \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u0442 \u043d\u0430 \u044d\u043c\u043e\u0434\u0437\u0438, \u043e\u043d \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u0432 \u043f\u043e\u043b\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f, \u0433\u0434\u0435 \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u043b\u0438\u0431\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u044d\u043c\u043e\u0434\u0437\u0438 \u043d\u0430\u0436\u0430\u0432 \u043d\u0430 Done, \u043b\u0438\u0431\u043e \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u043d\u0430\u0431\u043e\u0440 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p>\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c \u0440\u0435\u0430\u043a\u0446\u0438\u0439, \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u043a \u043f\u043e\u0441\u0442\u0430\u043c. \u0413\u043e\u0440\u0430\u0437\u0434\u043e \u043f\u0440\u043e\u0449\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u043c \u0441\u0430\u043c\u043e\u043c\u0443, \u0447\u0435\u043c \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c <strong>StreamFeedBloc<\/strong>. \u0422\u0435\u043f\u0435\u0440\u044c \u043b\u0438\u0441\u0442 \u0442\u0435\u0445, \u043a\u0442\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u043b \u043b\u0430\u0439\u043a \u043d\u0430 \u043f\u043e\u0441\u0442\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043d\u043e\u0432\u044b\u0439 \u043b\u0430\u0439\u043a.<\/p>\n<\/li>\n<\/ul>\n<p>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u0441\u0442\u043e\u0438\u0442 \u043c\u0435\u0442\u043e\u0434 <code>_addLikeReaction<\/code> \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435 <strong>_PictureCarousal<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0432\u0430\u043c \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c \u0438\u0437\u0443\u0447\u0438\u0442\u044c \u043f\u043e\u0433\u043b\u0443\u0431\u0436\u0435.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043f\u043e \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043a\u043e\u0434\u0430, \u043d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u043e \u0432\u0438\u0434\u0435\u043e, \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u043d\u043e\u0435 \u0432\u044b\u0448\u0435.<\/p>\n<p>\u0414\u0430\u043b\u0435\u0435, \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e, \u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b <code>components\/timeline\/widgets\/widgets.dart<\/code> \u0438 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u0435\u0433\u043e:<\/p>\n<pre><code class=\"dart\">export 'post_card.dart';<\/code><\/pre>\n<h2>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043b\u0435\u043d\u0442\u044b<\/h2>\n<p>\u0415\u0449\u0435 \u043e\u0434\u0438\u043d \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0440\u0430\u0437\u0434\u0435\u043b. \u041d\u0430 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435, \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0438\u0434\u0436\u0435\u0442 <strong>FlatFeedCore<\/strong> \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443.\u00a0 \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0435 \u0432 \u043b\u0435\u043d\u0442\u0435, \u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u043c \u0432\u0438\u0434\u0436\u0435\u0442 <strong>PostCard<\/strong>. \u0417\u0434\u0435\u0441\u044c, \u043d\u0430\u043c \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043c\u0430\u0433\u0438\u0438, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u043b\u0430\u0432\u0430\u044e\u0449\u0435\u0435 \u043f\u043e\u043b\u0435 \u00ab\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439\u00bb (<strong>CommentBox<\/strong>) \u043f\u0440\u0438 \u043f\u0440\u043e\u043b\u0438\u0441\u0442\u044b\u0432\u0430\u043d\u0438\u0438 \u043b\u0435\u043d\u0442\u044b.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b<strong> <\/strong><code>components\/timeline\/timeline_page.dart<\/code><strong> <\/strong>\u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435:<\/p>\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:stream_agram\/app\/app.dart'; import 'package:stream_agram\/components\/app_widgets\/app_widgets.dart'; import 'package:stream_feed_flutter_core\/stream_feed_flutter_core.dart';  import 'widgets\/widgets.dart';  \/\/\/ {@template timeline_page} \/\/\/ Page to display a timeline of user created posts. Global 'timeline' \/\/\/ {@endtemplate} class TimelinePage extends StatefulWidget {   \/\/\/ {@macro timeline_page}   const TimelinePage({Key? key}) : super(key: key);    @override   State&lt;TimelinePage> createState() => _TimelinePageState(); }  class _TimelinePageState extends State&lt;TimelinePage> {   final ValueNotifier&lt;bool> _showCommentBox = ValueNotifier(false);   final TextEditingController _commentTextController = TextEditingController();   final FocusNode _commentFocusNode = FocusNode();   EnrichedActivity? activeActivity;    void openCommentBox(EnrichedActivity activity, {String? message}) {     _commentTextController.text = message ?? '';     _commentTextController.selection = TextSelection.fromPosition(         TextPosition(offset: _commentTextController.text.length));     activeActivity = activity;     _showCommentBox.value = true;     _commentFocusNode.requestFocus();   }    Future&lt;void> addComment(String? message) async {     if (activeActivity != null &amp;&amp;         message != null &amp;&amp;         message.isNotEmpty &amp;&amp;         message != '') {       await FeedProvider.of(context).bloc.onAddReaction(         kind: 'comment',         activity: activeActivity!,         feedGroup: 'timeline',         data: {'message': message},       );       _commentTextController.clear();       FocusScope.of(context).unfocus();       _showCommentBox.value = false;     }   }    @override   void dispose() {     _commentTextController.dispose();     _commentFocusNode.dispose();     super.dispose();   }    @override   Widget build(BuildContext context) {     return GestureDetector(       onTap: () {         FocusScope.of(context).unfocus();         _showCommentBox.value = false;       },       child: Stack(         children: [           FlatFeedCore(             feedGroup: 'timeline',             errorBuilder: (context, error) =>                 const Text('Could not load profile'),             loadingBuilder: (context) => const SizedBox(),             emptyBuilder: (context) => const Center(               child: Text('No Posts\\nGo and post something'),             ),             flags: EnrichmentFlags()               ..withOwnReactions()               ..withRecentReactions()               ..withReactionCounts(),             feedBuilder: (context, activities) {               return RefreshIndicator(                 onRefresh: () {                   return FeedProvider.of(context).bloc.queryEnrichedActivities(                         feedGroup: 'timeline',                         flags: EnrichmentFlags()                           ..withOwnReactions()                           ..withRecentReactions()                           ..withReactionCounts(),                       );                 },                 child: ListView.builder(                   itemCount: activities.length,                   itemBuilder: (context, index) {                     return PostCard(                       key: ValueKey('post-${activities[index].id}'),                       enrichedActivity: activities[index],                       onAddComment: openCommentBox,                     );                   },                 ),               );             },           ),           _CommentBox(             commenter: context.appState.streamagramUser!,             textEditingController: _commentTextController,             focusNode: _commentFocusNode,             addComment: addComment,             showCommentBox: _showCommentBox,           )         ],       ),     );   } }  class _CommentBox extends StatefulWidget {   const _CommentBox({     Key? key,     required this.commenter,     required this.textEditingController,     required this.focusNode,     required this.addComment,     required this.showCommentBox,   }) : super(key: key);    final StreamagramUser commenter;   final TextEditingController textEditingController;   final FocusNode focusNode;   final Function(String?) addComment;   final ValueNotifier&lt;bool> showCommentBox;    @override   __CommentBoxState createState() => __CommentBoxState(); }  class __CommentBoxState extends State&lt;_CommentBox>     with SingleTickerProviderStateMixin {   late final AnimationController _controller;    late final Animation&lt;double> _animation = CurvedAnimation(     parent: _controller,     curve: Curves.easeOut,     reverseCurve: Curves.easeIn,   );    bool visibility = false;    @override   void initState() {     super.initState();     _controller = AnimationController(       vsync: this,       duration: const Duration(milliseconds: 300),     );     _controller.addStatusListener((status) {       if (status == AnimationStatus.dismissed) {         setState(() {           visibility = false;         });       } else {         setState(() {           visibility = true;         });       }     });     widget.showCommentBox.addListener(_showHideCommentBox);   }    void _showHideCommentBox() {     if (widget.showCommentBox.value) {       _controller.forward();     } else {       _controller.reverse();     }   }    @override   void dispose() {     super.dispose();     _controller.dispose();   }    @override   Widget build(BuildContext context) {     return Visibility(       visible: visibility,       child: FadeTransition(         opacity: _animation,         child: Builder(builder: (context) {           return Align(             alignment: Alignment.bottomCenter,             child: CommentBox(               commenter: widget.commenter,               textEditingController: widget.textEditingController,               focusNode: widget.focusNode,               onSubmitted: widget.addComment,             ),           );         }),       ),     );   } }<\/code><\/pre>\n<p>\u0421\u0430\u043c\u0430\u044f \u0432\u0430\u0436\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0440\u0430\u0437\u0434\u0435\u043b\u0430.<\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c <strong>Stack<\/strong>, \u043f\u0435\u0440\u0432\u044b\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u043c \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <strong>FlatFeedCore<\/strong>. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c, \u0434\u043b\u044f feedGroup \u043c\u044b \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u00ab<strong>timeline\u00bb<\/strong>. \u042d\u0442\u043e \u0432\u0441\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u0445\u043e\u0436\u0435 \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u043c\u044b \u0434\u0435\u043b\u0430\u043b\u0438 \u0434\u043b\u044f \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0444\u0438\u043b\u044f (<strong>ProfilePage<\/strong>). \u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0443\u0442 \u043c\u044b:<\/p>\n<ul>\n<li>\n<p>\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c PostCard \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435 <code>feedBuilder<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 <code>flags<\/code> \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 <strong>EnrichmentFlags<\/strong>. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u044d\u0442\u043e\u043c\u0443 <strong>FlatFeedCore<\/strong> \u0441\u043e\u0437\u0434\u0430\u0441\u0442 <strong>EnrichedActivities <\/strong>(\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u0441\u0442\u044b), \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432\u0441\u0435 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u0440\u0435\u0430\u043a\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439(<code>withOwnReactions<\/code>), \u0432\u0441\u0435 \u043d\u0435\u0434\u0430\u0432\u043d\u0438\u0435 \u0440\u0435\u0430\u043a\u0446\u0438\u0438(<code>withRecentReactions<\/code>)\u0438 \u0441\u0447\u0435\u0442\u0447\u0438\u043a \u0440\u0435\u0430\u043a\u0446\u0438\u0439 (<code>withReactionCounts<\/code>).<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u0438 \u0432 \u0442\u043e \u0436\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0432 <strong>Stack<\/strong> &#8212; \u044d\u0442\u043e<strong> _CommentBox<\/strong>. \u042d\u0442\u043e \u043e\u0441\u043e\u0431\u044b\u0439 \u0432\u0438\u0434\u0436\u0435\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u0430\u043c \u043f\u043b\u0430\u0432\u043d\u0443\u044e \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u0440\u0430\u0441\u043a\u0440\u044b\u0442\u0438\u044f \u043f\u043e\u043b\u044f \u00ab\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439\u00bb (<strong>CommentBox<\/strong>) \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043a\u043b\u0430\u0432\u0438\u0430\u0442\u0443\u0440\u043e\u0439 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043d\u0430 \u043d\u0435\u0433\u043e. \u041e\u043d \u0442\u0430\u043a\u0436\u0435 \u0441\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u044d\u0442\u043e \u043f\u043e\u043b\u0435 \u0435\u0441\u043b\u0438 \u043a\u043b\u0438\u043a\u043d\u0443\u0442\u044c \u043d\u0430 \u043b\u044e\u0431\u043e\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u043c\u0435\u0441\u0442\u043e.<\/p>\n<ul>\n<li>\n<p>\u0410\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 <code>_showCommentBox<\/code>.<\/p>\n<\/li>\n<\/ul>\n<p>\u0415\u0449\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0430\u0436\u043d\u044b\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u0432 \u043f\u0440\u043e \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b:<\/p>\n<ul>\n<li>\n<p>\u043c\u0435\u0442\u043e\u0434 <code>addComment<\/code> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <strong>FeedBloc<\/strong> \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0440\u0435\u0430\u043a\u0446\u0438\u0438 \u043d\u0430 \u043f\u043e\u0441\u0442 (<strong>activity<\/strong>). \u0420\u0435\u0430\u043a\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c &#8212; \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 (<strong>comment<\/strong>). \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043c\u044b \u0435\u0433\u043e \u0432 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443 (<strong>timeline<\/strong>). <strong>String<\/strong> \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u0435\u0442 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445;<\/p>\n<\/li>\n<li>\n<p>\u043c\u0435\u0442\u043e\u0434 <code>openCommentBox<\/code> \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u0442 \u043f\u0430\u043b\u044c\u0446\u0435\u043c \u043d\u0430 \u043f\u043e\u043b\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f \u0438\u043b\u0438 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 \u043e\u0434\u0438\u043d \u0438\u0437 \u044d\u043c\u043e\u0434\u0437\u0438. \u0414\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0442\u043e, \u0447\u0442\u043e\u0431\u044b <strong>CommentBox<\/strong> (\u043f\u043e\u043b\u0435 \u00ab\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439\u00bb) \u0431\u044b\u043b\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043e \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u043b\u043e\u0441\u044c \u0432 \u0444\u043e\u043a\u0443\u0441\u0435;\u00a0<\/p>\n<\/li>\n<li>\n<p>\u00a0<strong>_CommentBox <\/strong>\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u044f\u0432\u043d\u0443\u044e \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043c\u044b \u0445\u043e\u0442\u0438\u043c, \u0447\u0442\u043e\u0431\u044b \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 Visibility \u0431\u044b\u043b\u043e \u0440\u0430\u0432\u043d\u043e false \u0432 \u043c\u043e\u043c\u0435\u043d\u0442\u0435, \u043a\u043e\u0433\u0434\u0430 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f \u0438 \u043f\u043e\u043b\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0441\u043a\u0440\u044b\u0442\u043e. \u042d\u0442\u043e \u0443\u043b\u0443\u0447\u0448\u0430\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u043d\u0435 \u0445\u043e\u0442\u0438\u043c, \u0447\u0442\u043e\u0431\u044b Flutter \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043b \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f <strong>CommentBox<\/strong> \u0438, \u0434\u0430\u043b\u0435\u0435, \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u043b \u043d\u0443\u043b\u0435\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e\u0441\u0442\u0438 (opacity), \u043a\u043e\u0433\u0434\u0430 \u044d\u0442\u043e \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 <strong>FadeTransition<\/strong> &#8212; \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0439 \u0432\u043e Flutter;<\/p>\n<\/li>\n<\/ul>\n<p>\u0424\u0443\u0445 ?\u200d? \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0434\u043e\u0445\u043d\u0443\u0442\u044c. \u0412\u0441\u0451 \u043f\u043e\u0447\u0442\u0438 \u0433\u043e\u0442\u043e\u0432\u043e.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b <code>components\/timeline\/timeline.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435:<\/p>\n<pre><code class=\"dart\">export 'timeline_page.dart';<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435, \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435<strong> <\/strong><code>components\/home\/home_screen.dart<\/code><strong> <\/strong>\u0438 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e<strong> <\/strong><code>_homePages<\/code><strong> <\/strong>\u043a\u0430\u043a \u043d\u0430 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0435 \u043d\u0438\u0436\u0435:<\/p>\n<pre><code class=\"dart\">... import 'package:stream_agram\/components\/timeline\/timeline_page.dart'; ... \/\/\/ List of pages available from the home screen. static const List&lt;Widget> _homePages = &lt;Widget>[   TimelinePage(), \/\/ ADD THIS   SearchPage(),   ProfilePage(), ]; ...<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0441\u0432\u043e\u0439 Instagram \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u043f\u043e\u0441\u0442\u044b \u0432 \u0432\u0430\u0448\u0435\u0439 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043b\u0435\u043d\u0442\u0435 (\u0435\u0441\u043b\u0438 \u0432\u044b \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043b\u0438\u0441\u044c \u043d\u0430 \u043a\u043e\u0433\u043e-\u0442\u043e, \u043a\u0442\u043e \u0438\u0445 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043b). \u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043b\u0430\u0439\u043a\u0438 \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438, \u0430 \u043b\u0435\u043d\u0442\u0430 \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.<strong> <\/strong>?<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4d0\/098\/ce0\/4d0098ce08c75d7ab06f933d6e22032d.png\" width=\"293\" height=\"633\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4d0\/098\/ce0\/4d0098ce08c75d7ab06f933d6e22032d.png\"\/><\/figure>\n<h2>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 \u0438 \u043b\u0430\u0439\u043a\u043e\u0432<\/h2>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u044f \u043f\u043e\u0441\u0442 \u0432 Instagram*, \u0432\u044b \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043f\u043e\u0434 \u043d\u0438\u043c \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435. \u0412 \u043d\u0430\u0448\u0435\u043c \u043a\u043b\u043e\u043d\u0435 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u0435\u043a\u0446\u0438\u044e \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u043e \u0432\u0441\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043f\u043e\u0434 \u043f\u043e\u0441\u0442\u043e\u043c. \u041c\u044b \u0442\u0430\u043a\u0436\u0435 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u043b\u0438 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0438 \u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043b\u0430\u0439\u043a\u0438 \u043d\u0430 \u0441\u0430\u043c\u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 ?. \u042d\u0442\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u043e\u0447\u0435\u0440\u043d\u0438\u0435 \u0440\u0435\u0430\u043a\u0446\u0438\u0438.<\/p>\n<p>\u042d\u0442\u0430 \u0441\u0435\u043a\u0446\u0438\u044f &#8212; \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u044f \u0432\u0441\u0435\u0433\u043e, \u0447\u0442\u043e \u043c\u044b \u0438\u0437\u0443\u0447\u0438\u043b\u0438 \u0432\u044b\u0448\u0435.<\/p>\n<h2>\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f<\/h2>\n<p>\u0417\u0434\u0435\u0441\u044c \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0449\u0440\u0435\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0441\u0432\u043e\u0439 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 (\u0440\u0435\u0430\u043a\u0446\u0438\u044e) \u043a \u043f\u043e\u0441\u0442\u0443 \u0438\u043b\u0438 \u043a \u0434\u0440\u0443\u0433\u043e\u043c\u0443 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044e \u043f\u043e\u0434 \u043d\u0438\u043c.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 <code>components\/comments\/state\/comment_state.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435:<\/p>\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:stream_feed_flutter_core\/stream_feed_flutter_core.dart';  import '..\/..\/..\/app\/state\/models\/models.dart';  \/\/\/ Indicates the type of comment that was made. \/\/\/ Can be: \/\/\/ - Activity comment \/\/\/ - Reaction comment enum TypeOfComment {   \/\/\/ Comment on an activity   activityComment,    \/\/\/ Comment on a reaction   reactionComment, }  \/\/\/ {@template comment_focus} \/\/\/ Information on the type of comment to make. This can be a comment on an \/\/\/ activity, or a comment on a reaction. \/\/\/ \/\/\/ It also indicates the parent user on whom the comment is made. \/\/\/ {@endtemplate} class CommentFocus {   \/\/\/ {@macro comment_focus}   const CommentFocus({     required this.typeOfComment,     required this.id,     required this.user,     this.reaction,   });    final Reaction? reaction;    \/\/\/ Indicates the type of comment. See [TypeOfComment].   final TypeOfComment typeOfComment;    \/\/\/ Activity or reaction id on which the comment is made.   final String id;    \/\/\/ The user data of the parent activity or reaction.   final StreamagramUser user; }  \/\/\/ {@template comment_state} \/\/\/ ChangeNotifier to facilitate posting comments to activities and reactions. \/\/\/ {@endtemplate} class CommentState extends ChangeNotifier {   \/\/\/ {@macro comment_state}   CommentState({     required this.activityId,     required this.activityOwnerData,   });    \/\/\/ The id for this activity.   final String activityId;    \/\/\/ UserData of whoever owns the activity.   final StreamagramUser activityOwnerData;    \/\/\/ The type of commentFocus that is currently selected.   late CommentFocus commentFocus = CommentFocus(     typeOfComment: TypeOfComment.activityComment,     id: activityId,     user: activityOwnerData,   );    \/\/\/ Sets the focus to which a comment will be posted to.   \/\/\/   \/\/\/ See [postComment].   void setCommentFocus(CommentFocus focus) {     commentFocus = focus;     notifyListeners();   }    \/\/\/ Resets the comment focus to the parent activity.   void resetCommentFocus() {     commentFocus = CommentFocus(       typeOfComment: TypeOfComment.activityComment,       id: activityId,       user: activityOwnerData,     );     notifyListeners();   } }<\/code><\/pre>\n<p>\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435<strong> ChangeNotifier <\/strong>\u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0442\u0438\u043f\u0430 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f (<strong>TypeOfComment<\/strong>). \u0415\u0441\u0442\u044c \u0434\u0432\u0430 \u0442\u0438\u043f\u0430 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432:<\/p>\n<ul>\n<li>\n<p>\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 (\u043b\u0438\u0431\u043e \u043b\u0430\u0439\u043a) \u043a \u043f\u043e\u0441\u0442\u0443;<\/p>\n<\/li>\n<li>\n<p>\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 (\u043b\u0438\u0431\u043e \u043b\u0430\u0439\u043a) \u043a \u0434\u0440\u0443\u0433\u043e\u043c\u0443 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044e.<\/p>\n<\/li>\n<\/ul>\n<p>\u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0449\u0435 \u043f\u043e\u043d\u044f\u0442\u044c, \u0432\u0437\u0433\u043b\u044f\u043d\u0443\u0432 \u043d\u0430 UI \u043a\u043e\u0434.<\/p>\n<p>\u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b\u0443 <code>components\/comments\/state\/state.dart<\/code>:<\/p>\n<pre><code class=\"dart\">export 'comment_state.dart';<\/code><\/pre>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0441\u0435\u043a\u0446\u0438\u044e \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 \u043a\u0430\u043a \u0432 Instagram*<\/h2>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 <code>components\/comments\/comment_screen.dart<\/code><strong> <\/strong>\u0444\u0430\u0439\u043b \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435:<\/p>\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:jiffy\/jiffy.dart'; import 'package:provider\/provider.dart'; import 'package:stream_feed_flutter_core\/stream_feed_flutter_core.dart';  import '..\/..\/app\/app.dart'; import '..\/app_widgets\/app_widgets.dart'; import 'state\/comment_state.dart';  \/\/\/ Screen that shows all comments for a given post. class CommentsScreen extends StatefulWidget {   \/\/\/ Creates a new [CommentsScreen].   const CommentsScreen({     Key? key,     required this.enrichedActivity,     required this.activityOwnerData,   }) : super(key: key);    final EnrichedActivity enrichedActivity;    \/\/\/ Owner \/ [User] of the activity.   final StreamagramUser activityOwnerData;    \/\/\/ MaterialPageRoute to this screen.   static Route route({     required EnrichedActivity enrichedActivity,     required StreamagramUser activityOwnerData,   }) =>       MaterialPageRoute(         builder: (context) => CommentsScreen(           enrichedActivity: enrichedActivity,           activityOwnerData: activityOwnerData,         ),       );    @override   _CommentsScreenState createState() => _CommentsScreenState(); }  class _CommentsScreenState extends State&lt;CommentsScreen> {   late FocusNode commentFocusNode;   late CommentState commentState;    @override   void initState() {     super.initState();     commentFocusNode = FocusNode();     commentState = CommentState(       activityId: widget.enrichedActivity.id!,       activityOwnerData: widget.activityOwnerData,     );   }    @override   void dispose() {     commentFocusNode.dispose();     super.dispose();   }    @override   Widget build(BuildContext context) {     return MultiProvider(       providers: [         ChangeNotifierProvider.value(value: commentState),         ChangeNotifierProvider.value(value: commentFocusNode),       ],       child: GestureDetector(         onTap: () {           commentState.resetCommentFocus();           FocusScope.of(context).unfocus();         },         child: Scaffold(           appBar: AppBar(             title: const Text('Comments',                 style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),             elevation: 0.5,             shadowColor: Colors.white,           ),           body: Stack(             children: [               _CommentsList(                 activityId: widget.enrichedActivity.id!,               ),               _CommentBox(                 enrichedActivity: widget.enrichedActivity,               ),             ],           ),         ),       ),     );   } }  class _CommentsList extends StatelessWidget {   const _CommentsList({     Key? key,     required this.activityId,   }) : super(key: key);    final String activityId;    @override   Widget build(BuildContext context) {     return ReactionListCore(       lookupValue: activityId,       kind: 'comment',       loadingBuilder: (context) =>           const Center(child: CircularProgressIndicator()),       errorBuilder: (context, error) =>           const Center(child: Text('Could not load comments.')),       emptyBuilder: (context) =>           const Center(child: Text('Be the first to add a comment.')),       reactionsBuilder: (context, reactions) {         return ListView.builder(           itemCount: reactions.length + 1,           itemBuilder: (context, index) {             if (index == reactions.length) {               \/\/ Bottom padding to ensure [CommentBox] does not obscure               \/\/ visibility               return const SizedBox(                 height: 120,               );             }             return Padding(               padding: const EdgeInsets.symmetric(vertical: 8.0),               child: _CommentTile(                 key: ValueKey('comment-${reactions[index].id}'),                 reaction: reactions[index],               ),             );           },         );       },       flags: EnrichmentFlags()         ..withOwnChildren()         ..withOwnReactions()         ..withRecentReactions(),     );   } }  class _CommentBox extends StatefulWidget {   const _CommentBox({     Key? key,     required this.enrichedActivity,   }) : super(key: key);    final EnrichedActivity enrichedActivity;    @override   __CommentBoxState createState() => __CommentBoxState(); }  class __CommentBoxState extends State&lt;_CommentBox> {   late final _commentTextController = TextEditingController();    Future&lt;void> handleSubmit(String? value) async {     if (value != null &amp;&amp; value.isNotEmpty) {       _commentTextController.clear();       FocusScope.of(context).unfocus();        final commentState = context.read&lt;CommentState>();       final commentFocus = commentState.commentFocus;        if (commentFocus.typeOfComment == TypeOfComment.activityComment) {         await FeedProvider.of(context).bloc.onAddReaction(           kind: 'comment',           activity: widget.enrichedActivity,           feedGroup: 'timeline',           data: {'message': value},         );       } else if (commentFocus.typeOfComment == TypeOfComment.reactionComment) {         if (commentFocus.reaction != null) {           await FeedProvider.of(context).bloc.onAddChildReaction(             kind: 'comment',             reaction: commentFocus.reaction!,             activity: widget.enrichedActivity,             data: {'message': value},           );         }       }     }   }    @override   void dispose() {     _commentTextController.dispose();     super.dispose();   }    @override   Widget build(BuildContext context) {     final commentFocus =         context.select((CommentState state) => state.commentFocus);      final focusNode = context.watch&lt;FocusNode>();      return Align(       alignment: Alignment.bottomCenter,       child: Container(         color: (Theme.of(context).brightness == Brightness.light)             ? AppColors.light             : AppColors.dark,         child: Column(           mainAxisSize: MainAxisSize.min,           children: [             AnimatedSwitcher(               duration: const Duration(milliseconds: 200),               transitionBuilder: (child, animation) {                 final tween =                     Tween(begin: const Offset(0.0, 1.0), end: Offset.zero)                         .chain(CurveTween(curve: Curves.easeOutQuint));                 final offsetAnimation = animation.drive(tween);                 return SlideTransition(                   position: offsetAnimation,                   child: child,                 );               },               child:                   (commentFocus.typeOfComment == TypeOfComment.reactionComment)                       ? _replyToBox(commentFocus, context)                       : const SizedBox.shrink(),             ),             CommentBox(               commenter: context.appState.streamagramUser!,               textEditingController: _commentTextController,               onSubmitted: handleSubmit,               focusNode: focusNode,             ),             SizedBox(               height: MediaQuery.of(context).padding.bottom,             )           ],         ),       ),     );   }    Container _replyToBox(CommentFocus commentFocus, BuildContext context) {     return Container(       color: (Theme.of(context).brightness == Brightness.dark)           ? AppColors.grey           : AppColors.ligthGrey,       child: Padding(         padding: const EdgeInsets.all(16.0),         child: Row(           children: [             Text(               'Replying to ${commentFocus.user.fullName}',               style: AppTextStyle.textStyleFaded,             ),             const Spacer(),             TapFadeIcon(               onTap: () {                 context.read&lt;CommentState>().resetCommentFocus();               },               icon: Icons.close,               size: 16,               iconColor: Theme.of(context).iconTheme.color!,             ),           ],         ),       ),     );   } }  class _CommentTile extends StatefulWidget {   const _CommentTile({     Key? key,     required this.reaction,     this.canReply = true,     this.isReplyToComment = false,   }) : super(key: key);    final Reaction reaction;   final bool canReply;   final bool isReplyToComment;   @override   __CommentTileState createState() => __CommentTileState(); }  class __CommentTileState extends State&lt;_CommentTile> {   late final userData = StreamagramUser.fromMap(widget.reaction.user!.data!);   late final message = extractMessage;    late final timeSince = _timeSinceComment();    late int numberOfLikes = widget.reaction.childrenCounts?['like'] ?? 0;    late bool isLiked = _isFavorited();   Reaction? likeReaction;    String _timeSinceComment() {     final jiffyTime = Jiffy(widget.reaction.createdAt).fromNow();     if (jiffyTime == 'a few seconds ago') {       return 'just now';     } else {       return jiffyTime;     }   }    String numberOfLikesMessage(int count) {     if (count == 0) {       return '';     }     if (count == 1) {       return '1 like';     } else {       return '$count likes';     }   }    String get extractMessage {     final data = widget.reaction.data;     if (data != null &amp;&amp; data['message'] != null) {       return data['message'] as String;     } else {       return '';     }   }    bool _isFavorited() {     likeReaction = widget.reaction.ownChildren?['like']?.first;     return likeReaction != null;   }    Future&lt;void> _handleFavorite(bool liked) async {     if (isLiked &amp;&amp; likeReaction != null) {       await context.appState.client.reactions.delete(likeReaction!.id!);       numberOfLikes--;     } else {       likeReaction = await context.appState.client.reactions.addChild(         'like',         widget.reaction.id!,         userId: context.appState.user.id,       );       numberOfLikes++;     }     setState(() {       isLiked = liked;     });   }    @override   Widget build(BuildContext context) {     return Column(       mainAxisSize: MainAxisSize.min,       crossAxisAlignment: CrossAxisAlignment.start,       children: [         Row(           crossAxisAlignment: CrossAxisAlignment.start,           children: [             Padding(               padding: const EdgeInsets.symmetric(horizontal: 8.0),               child: (widget.isReplyToComment)                   ? Avatar.tiny(streamagramUser: userData)                   : Avatar.small(streamagramUser: userData),             ),             Expanded(               child: Column(                 crossAxisAlignment: CrossAxisAlignment.start,                 mainAxisSize: MainAxisSize.min,                 children: [                   Row(                     children: [                       Expanded(                         child: Text.rich(                           TextSpan(                             children: &lt;TextSpan>[                               TextSpan(                                   text: userData.fullName,                                   style: AppTextStyle.textStyleSmallBold),                               const TextSpan(text: ' '),                               TextSpan(                                 text: message,                                 style: const TextStyle(fontSize: 13),                               ),                             ],                           ),                         ),                       ),                       Padding(                         padding: const EdgeInsets.symmetric(horizontal: 8.0),                         child: Center(                           child: FavoriteIconButton(                             isLiked: isLiked,                             size: 14,                             onTap: _handleFavorite,                           ),                         ),                       )                     ],                   ),                   Padding(                     padding: const EdgeInsets.only(top: 4.0),                     child: Row(                       mainAxisAlignment: MainAxisAlignment.start,                       children: [                         SizedBox(                           width: 80,                           child: Text(                             timeSince,                             style: AppTextStyle.textStyleFadedSmall,                           ),                         ),                         Visibility(                           visible: numberOfLikes > 0,                           child: SizedBox(                             width: 60,                             child: Text(                               numberOfLikesMessage(numberOfLikes),                               style: AppTextStyle.textStyleFadedSmall,                             ),                           ),                         ),                         Visibility(                           visible: widget.canReply,                           child: GestureDetector(                             onTap: () {                               context.read&lt;CommentState>().setCommentFocus(                                     CommentFocus(                                       typeOfComment:                                           TypeOfComment.reactionComment,                                       id: widget.reaction.id!,                                       user: StreamagramUser.fromMap(                                           widget.reaction.user!.data!),                                       reaction: widget.reaction,                                     ),                                   );                                FocusScope.of(context)                                   .requestFocus(context.read&lt;FocusNode>());                             },                             child: const SizedBox(                               width: 50,                               child: Text(                                 'Reply',                                 style: AppTextStyle.textStyleFadedSmallBold,                               ),                             ),                           ),                         )                       ],                     ),                   ),                 ],               ),             ),           ],         ),         Padding(           padding: const EdgeInsets.only(left: 34.0),           child: _ChildCommentList(               comments: widget.reaction.latestChildren?['comment']),         ),       ],     );   } }  class _ChildCommentList extends StatelessWidget {   const _ChildCommentList({     Key? key,     required this.comments,   }) : super(key: key);    final List&lt;Reaction>? comments;    @override   Widget build(BuildContext context) {     return Column(       mainAxisSize: MainAxisSize.min,       children: comments               ?.map(                 (reaction) => Padding(                   padding: const EdgeInsets.only(top: 8.0),                   child: _CommentTile(                     key: ValueKey('comment-tile-${reaction.id}'),                     reaction: reaction,                     canReply: false,                     isReplyToComment: true,                   ),                 ),               )               .toList() ??           [],     );   } }<\/code><\/pre>\n<p>\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0430\u0436\u043d\u044b\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u0432 \u043f\u0440\u043e \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b:<\/p>\n<ul>\n<li>\n<p>\u0437\u0434\u0435\u0441\u044c \u043c\u044b \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0434\u043e\u0441\u0442\u0443\u043f \u043a <strong>FocusNode<\/strong> \u0438 <strong>CommentState<\/strong>, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f\u00a0 <strong>MultiProvider<\/strong> \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0434\u043e\u0447\u0435\u0440\u043d\u0438\u0445 \u0432\u0438\u0434\u0436\u0435\u0442\u043e\u0432 <strong>CommentScreen<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0435\u043c <strong>Stack<\/strong> \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 private \u0432\u0438\u0434\u0436\u0435\u0442\u043e\u0432 <strong>_CommentList<\/strong> \u0438 <strong>_CommentBox<\/strong>;<\/p>\n<\/li>\n<li>\n<p><strong>_CommentList <\/strong>\u0437\u0434\u0435\u0441\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <strong>ReactionListCore<\/strong> \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u0432\u0441\u0435 \u0440\u0435\u0430\u043a\u0446\u0438\u0438 \u043a \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c\u0443 \u043f\u043e\u0441\u0442\u0443 (<strong>Activity<\/strong>). \u041f\u043e \u0442\u0430\u043a\u043e\u043c\u0443 \u0436\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0443 \u043c\u044b \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c <strong>EnrichmentFlags<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0442\u0430\u043a\u0436\u0435 \u043c\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 <strong>AnimatedSwitcher<\/strong> \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u044e\u0449\u0435\u0435 \u043e\u043a\u043d\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u043c \u043d\u0430 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f;<\/p>\n<\/li>\n<li>\n<p>\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043c\u0435\u0442\u043e\u0434<strong> <\/strong><code>_handleFavorite<\/code>, \u0447\u0442\u043e\u0431\u044b \u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043b\u0430\u0439\u043a\u0438 \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438;<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0432\u043e\u0434\u0438\u043c \u043d\u0430 \u043f\u043e\u043a\u0430\u0437 \u0432\u043e\u0437\u0440\u0430\u0441\u0442 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043f\u0430\u043a\u0435\u0442\u0430 <strong>Jiffy<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u043c\u0435\u0442\u043e\u0434 <code>handleSubmit<\/code> \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u0438\u043b\u0438 \u0434\u043e\u0447\u0435\u0440\u043d\u0438\u043c \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0432 \u0441\u0435\u043a\u0446\u0438\u044e \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f <strong>FeedBloc<\/strong>.<\/p>\n<\/li>\n<\/ul>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/252\/bfa\/25e\/252bfa25e153554007b737eb688202b7.png\" width=\"293\" height=\"633\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/252\/bfa\/25e\/252bfa25e153554007b737eb688202b7.png\"\/><\/figure>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b <code>components\/comments\/comments.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435:<\/p>\n<pre><code class=\"dart\">export 'comment_screen.dart';<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0438\u0441\u043f\u0440\u0430\u0432\u044c\u0442\u0435 \u0442\u0435\u043a\u0441\u0442 \u0432 \u043f\u043e\u043b\u044f\u0445 TODO \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 \u0432 <code>components\/timeline\/widgets\/post_card.dart<\/code>, \u0447\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u043a \u044d\u043a\u0440\u0430\u043d\u0443 \u0441\u0435\u043a\u0446\u0438\u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 <strong>CommentsScreen<\/strong>.<\/p>\n<pre><code class=\"dart\">...  import '..\/..\/comments\/comments.dart';  ...  Padding(   padding: iconPadding,   child: TapFadeIcon(     onTap: () { \/\/ ADD THIS       final map = widget.enrichedActivity.actor!.data!;  \/\/ AND THIS       Navigator.of(context).push(         CommentsScreen.route(           enrichedActivity: widget.enrichedActivity,           activityOwnerData: StreamagramUser.fromMap(map),         ),       );     },     icon: Icons.chat_bubble_outline,     iconColor: iconColor,   ), ),  ...  if (commentCount > 2)   Padding(     padding: spacePadding,     child: GestureDetector(       onTap: () { \/\/ ADD THIS         final map =             widget.enrichedActivity.actor!.data as Map&lt;String, dynamic>; \/\/ AND THIS         Navigator.of(context).push(CommentsScreen.route(           enrichedActivity: widget.enrichedActivity,           activityOwnerData: StreamagramUser.fromMap(map),         ));       },       child: Text(         'View all $commentCount comments',         style: AppTextStyle.textStyleFaded,       ),     ),   ),<\/code><\/pre>\n<h2>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0432\u043e\u044e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0443\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443 \u0432 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e<\/h2>\n<p>\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u044b \u0443\u0436\u0435 \u0437\u0430\u043c\u0435\u0442\u0438\u043b\u0438, \u0447\u0442\u043e\u00a0 \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443, \u043c\u044b \u043d\u0435 \u0432\u0438\u0434\u0438\u043c \u043f\u043e\u0441\u0442\u044b \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<p>\u042d\u0442\u043e \u0432\u0441\u0435 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0435\u0449\u0435 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442, \u0447\u0442\u043e \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f \u0438 \u043d\u0430 \u0441\u0432\u043e\u044e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0443\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443 \u0442\u043e\u0436\u0435.<\/p>\n<p>\u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 <code>app\/state\/app_state.dart<\/code> \u0438 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u0435\u0433\u043e, \u043a\u0430\u043a \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0438\u0436\u0435:<\/p>\n<pre><code class=\"dart\">...  \/\/\/ Current user's [FlatFeed] with name 'user'. \/\/\/ \/\/\/ This feed contains all of a user's personal posts. FlatFeed get currentUserFeed => _client.flatFeed('user', user.id);  \/\/\/ Current user's [FlatFeed] with name 'timeline'. \/\/\/ \/\/\/ This contains all posts that a user has subscribed (followed) to. FlatFeed get currentTimelineFeed => _client.flatFeed('timeline', user.id);  ...<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435, \u0432\u043d\u0435\u0441\u0438\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u043c\u0435\u0442\u043e\u0434 <code>connect<\/code>, \u0447\u0442\u043e\u0431\u044b \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0438 \u0437\u0430 \u0441\u0432\u043e\u0435\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u043b\u0435\u043d\u0442\u043e\u0439:<\/p>\n<pre><code class=\"dart\">Future&lt;bool> connect(DemoAppUser demoUser) async {     final currentUser = await _client.setUser(       User(id: demoUser.id),       demoUser.token!,       extraData: demoUser.data,     );     if (currentUser.data != null) {       _streamagramUser = StreamagramUser.fromMap(currentUser.data!);       await currentTimelineFeed.follow(currentUserFeed); \/\/ ADD THIS -> Follow own feed       notifyListeners();       return true;     } else {       return false;     }   }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0435\u0441\u043b\u0438 \u0432\u044b \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438 \u0437\u0430\u0439\u0434\u0435\u0442\u0435 \u043a\u0430\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u0438\u0434\u0435\u0442\u044c \u0438 \u0441\u0432\u043e\u0438 \u043f\u043e\u0441\u0442\u044b \u0432 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043b\u0435\u043d\u0442\u0435.<\/p>\n<h2>\u041a\u044d\u0448\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446<\/h2>\n<p>\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u044b \u0435\u0449\u0435 \u0437\u0430\u043c\u0435\u0442\u0438\u043b\u0438, \u0447\u0442\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0437\u0430\u043d\u043e\u0432\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u043a\u043e\u0433\u0434\u0430 \u0432\u044b \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0435 \u0438\u0445 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f <strong>PageView<\/strong>). \u041d\u0435\u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0430\u0435\u0442, \u043d\u0435 \u0442\u0430\u043a \u043b\u0438?<\/p>\n<p>\u041a \u0441\u0447\u0430\u0441\u0442\u044c\u044e, \u0435\u0441\u0442\u044c \u043b\u0435\u0433\u043a\u0438\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043a\u0430\u043a \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u044d\u0442\u043e \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c.<\/p>\n<p>\u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u0444\u0430\u0439\u043b <code>components\/home\/home_screen.dart<\/code> \u0432 \u0441\u0430\u043c\u043e\u043c \u0435\u0433\u043e \u043d\u0438\u0437\u0443 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441:<\/p>\n<pre><code class=\"dart\">class _KeepAlivePage extends StatefulWidget {   const _KeepAlivePage({     Key? key,     required this.child,   }) : super(key: key);    final Widget child;    @override   _KeepAlivePageState createState() => _KeepAlivePageState(); }  class _KeepAlivePageState extends State&lt;_KeepAlivePage>     with AutomaticKeepAliveClientMixin {   @override   Widget build(BuildContext context) {     super.build(context);      return widget.child;   }    @override   bool get wantKeepAlive => true; }<\/code><\/pre>\n<p>\u041e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <strong>AutomaticKeepAliveClientMixin<\/strong> \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0432\u0438\u0434\u0436\u0435\u0442\u044b \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a <strong>PageView<\/strong> \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442 \u0441 \u043d\u0438\u043c\u0438.<\/p>\n<p>\u0412\u0441\u0435 \u0447\u0442\u043e \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u0441\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435 <code>_KeepAlivePage<\/code>. \u0418\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u0432\u0438\u0434\u0436\u0435\u0442 <code>_homePages<\/code> \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u043a\u043e\u0434\u043e\u043c \u043d\u0438\u0436\u0435:<\/p>\n<pre><code class=\"dart\">\/\/\/ List of pages available from the home screen. static const List&lt;Widget> _homePages = &lt;Widget>[   _KeepAlivePage(child: TimelinePage()),   _KeepAlivePage(child: SearchPage()),   _KeepAlivePage(child: ProfilePage()), ];<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u0432\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0431\u044b\u0441\u0442\u0440\u0443\u044e \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c, \u0447\u0442\u043e \u0432\u0430\u0448\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043d\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c\u0441\u044f \u0437\u0430\u043d\u043e\u0432\u043e \u043f\u043e \u043c\u0435\u0440\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u0432\u044b \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0435\u0441\u044c \u043c\u0435\u0436\u0434\u0443 \u0432\u043a\u043b\u0430\u0434\u043a\u0430\u043c\u0438.<\/p>\n<h2>\u0427\u0442\u043e \u0434\u0430\u043b\u044c\u0448\u0435?<\/h2>\n<p>\u0412\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0433\u043e\u0442\u043e\u0432\u043e!<strong> <\/strong>?<\/p>\n<p>\u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 Flutter \u0438 Stream Feeds \u0432\u044b \u0441\u043c\u043e\u0433\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435-\u043a\u043b\u043e\u043d Instagram.\u00a0 \u041f\u043e\u0437\u0434\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432\u0430\u0441 \u0441 \u044d\u0442\u0438\u043c!<\/p>\n<p>\u041a\u0430\u043a \u0443\u0436\u0435 \u043d\u0435 \u0440\u0430\u0437 \u0431\u044b\u043b\u043e \u0441\u043a\u0430\u0437\u0430\u043d\u043e \u0440\u0430\u043d\u0435\u0435, \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043b\u0435\u043d\u0442\u0430\u043c\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u044b\u043c, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c Feeds \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u0435\u0449\u0435 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u043d\u0435 \u0443\u0441\u043f\u0435\u043b\u0438 \u0437\u0430\u0442\u0440\u043e\u043d\u0443\u0442\u044c \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0448\u0430\u0433\u043e\u043c \u0434\u043b\u044f \u0432\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0433\u043e, \u0447\u0442\u043e \u0432\u044b \u0443\u0437\u043d\u0430\u043b\u0438 \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435, \u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u0432\u043e\u0438\u0445 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0441 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u043e\u043c Feeds. \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c \u0432\u0430\u043c \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u043f\u0440\u043e \u0434\u0440\u0443\u0433\u0438\u0435 \u0442\u0438\u043f\u044b \u043b\u0435\u043d\u0442 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 Aggregated \u0438 Notification), \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u0442\u044c \u0443\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u043c \u043d\u0443\u0436\u043d\u044b \u043b\u0438 \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0432\u0430\u0448\u0435\u043c\u0443 \u043d\u043e\u0432\u043e\u043c\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e.<\/p>\n<p>\u041c\u044b \u0432\u0441\u0435\u0433\u0434\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u044b \u0434\u043b\u044f \u0444\u0438\u0434\u0431\u044d\u043a\u0430 \u0438 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0439. \u041f\u0438\u0448\u0438\u0442\u0435 \u043d\u0430\u043c \u043f\u0440\u043e \u0447\u0442\u043e \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0432\u0438\u0434\u0435\u043e \u0438 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c.<\/p>\n<p>\u041f\u043e\u043b\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u043a\u043e\u0434\u0430 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u043a\u0430\u0447\u0430\u0442\u044c <a href=\"https:\/\/github.com\/GetStream\/flutter-instagram-clone\" rel=\"noopener noreferrer nofollow\">\u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435<\/a> \u043d\u0430 Github.<\/p>\n<p><em>Instagram* &#8212; \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043d\u0430\u044f \u0432 \u0420\u043e\u0441\u0441\u0438\u0438 \u0441\u043e\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u0435\u0442\u044c.<\/em><\/p>\n<p><em>\u0422\u0430\u043a\u0436\u0435 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043c\u043e\u0439 \u043a\u0430\u043d\u0430\u043b \u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b \u043c\u043e\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<br \/>\u0421\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c:\u00a0<\/em><a href=\"https:\/\/t.me\/dom24x7\" rel=\"noopener noreferrer nofollow\"><em>https:\/\/t.me\/dom24x7<\/em><\/a><em><br \/>\u0421\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043c\u043e\u0439 \u043a\u0430\u043d\u0430\u043b:\u00a0<\/em><a href=\"https:\/\/t.me\/evgaj\" rel=\"noopener noreferrer nofollow\"><em>https:\/\/t.me\/evgaj<\/em><\/a><\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/733748\/\"> https:\/\/habr.com\/ru\/articles\/733748\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442! \u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u044e \u0432\u044b\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u0441\u0442\u0430\u0442\u044c\u0438, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u043a\u0430\u043a \u043e\u0441\u043d\u043e\u0432\u0443 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043e\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430 \u0432 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435\u00a0<a href=\"https:\/\/dom24x7.ru\/\" rel=\"noopener noreferrer nofollow\">Dom24x7<\/a>, \u0433\u0434\u0435 \u043b\u044e\u0434\u0438 \u043c\u043e\u0433\u0443\u0442 \u043e\u0431\u0449\u0430\u0442\u044c\u0441\u044f \u0434\u0440\u0443\u0433 \u0441 \u0434\u0440\u0443\u0433\u043e\u043c, \u0440\u0435\u0448\u0430\u0442\u044c \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0449\u0438\u0435 \u0431\u044b\u0442\u043e\u0432\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u0423\u041a\/\u0422\u0421\u0416. \u041f\u0435\u0440\u0432\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0441\u0442\u0430\u0442\u044c\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c\u00a0<a href=\"https:\/\/habr.com\/ru\/articles\/727794\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>, \u0430 \u0432\u0442\u043e\u0440\u0443\u044e \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 <a href=\"https:\/\/habr.com\/ru\/articles\/730104\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>.<\/p>\n<p>\u0418\u0442\u0430\u043a&#8230;<\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0438\u043a\u043e\u043d\u043a\u0443 \u0443\u0433\u0430\u0441\u0430\u043d\u0438\u044f<\/h2>\n<p>\u041f\u0440\u0438\u0448\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0434\u043b\u044f \u0435\u0449\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430. \u042d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0432\u0438\u0434\u0436\u0435\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0443\u0433\u0430\u0441\u0430\u043d\u0438\u0435 \u0438\u043a\u043e\u043d\u043a\u0438, \u0438\u043b\u0438 \u043d\u0430\u0441\u044b\u0449\u0435\u043d\u0438\u0435 \u0435\u0435 \u0446\u0432\u0435\u0442\u0430, \u043f\u0443\u0442\u0435\u043c \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043d\u0430 \u043d\u0435\u0435.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 <code>components\/app_widgets\/tap_fade_icon.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code class=\"dart\">import 'package:flutter\/material.dart';  \/\/\/ {@template tap_fade_icon} \/\/\/ A tappable icon that fades colors when tapped and held. \/\/\/ {@endtemplate} class TapFadeIcon extends StatefulWidget {   \/\/\/ {@macro tap_fade_icon}   const TapFadeIcon({     Key? key,     required this.onTap,     required this.icon,     required this.iconColor,     this.size = 22,   }) : super(key: key);    \/\/\/ Callback to handle tap.   final VoidCallback onTap;    \/\/\/ Color of the icon.   final Color iconColor;    \/\/\/ Type of icon.   final IconData icon;    \/\/\/ Icon size.   final double size;    @override   _TapFadeIconState createState() => _TapFadeIconState(); }  class _TapFadeIconState extends State&lt;TapFadeIcon> {   late Color color = widget.iconColor;    void handleTapDown(TapDownDetails _) {     setState(() {       color = widget.iconColor.withOpacity(0.7);     });   }    void handleTapUp(TapUpDetails _) {     setState(() {       color = widget.iconColor;     });      widget.onTap(); \/\/ Execute callback.   }    @override   void didUpdateWidget(covariant TapFadeIcon oldWidget) {     super.didUpdateWidget(oldWidget);     if (oldWidget.iconColor != widget.iconColor) {       color = widget.iconColor;     }   }    @override   Widget build(BuildContext context) {     return GestureDetector(       onTapDown: handleTapDown,       onTapUp: handleTapUp,       child: Icon(         widget.icon,         color: color,         size: widget.size,       ),     );   } }<\/code><\/pre>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c\u0441\u044f \u043d\u0430 \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u0432\u0438\u0434\u0436\u0435\u0442\u0430 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u044d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u0432 \u043d\u0443\u0436\u043d\u044b\u0439 \u0431\u0430\u0440\u0440\u0435\u043b\u044c-\u0444\u0430\u0439\u043b. \u041e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 <code>components\/app_widgets\/app_widgets.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043d\u0438\u0436\u0435\u043b\u0435\u0436\u0430\u0449\u0438\u0439 \u043a\u043e\u0434:<\/p>\n<pre><code class=\"dart\">export 'avatars.dart'; export 'tap_fade_icon.dart'; \/\/ ADD THIS<\/code><\/pre>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443<\/h2>\n<p>\u041a\u0430\u043a \u0443\u0436\u0435 \u0431\u044b\u043b\u043e \u0443\u043f\u043e\u043c\u044f\u043d\u0443\u0442\u043e \u0440\u0430\u043d\u0435\u0435, \u043d\u0430\u0448 \u0438\u043d\u0441\u0442\u0430\u0433\u0440\u0430\u043c \u043a\u043b\u043e\u043d \u043c\u043e\u0436\u043d\u043e \u0442\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0440\u0430\u0437\u0431\u0438\u0442\u044c \u043d\u0430 \u0434\u0432\u0435 \u0447\u0430\u0441\u0442\u0438: \u043d\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e (<strong>user<\/strong>) \u0438 \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0443\u044e (<strong>timeline<\/strong>) \u043b\u0435\u043d\u0442\u044b. \u041a\u0430\u0436\u0434\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u043c\u0435\u0435\u0442 \u0441\u0432\u043e\u044e \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443, \u0433\u0434\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u044e\u0442\u0441\u044f \u0432\u0441\u0435 \u043f\u043e\u0441\u0442\u044b, \u0441\u0434\u0435\u043b\u0430\u043d\u043d\u044b\u0435 \u0438\u043c; \u0432 \u0442\u043e \u0432\u0440\u0435\u043c\u044f \u043a\u0430\u043a \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043b\u0435\u043d\u0442\u0430 \u044d\u0442\u043e \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u044f \u0432\u0441\u0435\u0445 \u043b\u0435\u043d\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0432\u044b \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u044b.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0435\u043a\u0446\u0438\u0438, \u0432\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u0442\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u0439 \u043f\u0440\u043e\u0434\u0432\u0438\u0433\u0430\u0442\u044c <strong>\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f<\/strong> (\u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 &#8212; \u043f\u043e\u0441\u0442\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f) \u043f\u0440\u044f\u043c\u0438\u043a\u043e\u043c \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043b\u0435\u043d\u0442\u0443.<\/p>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u0448 \u043a\u043b\u043e\u043d Instagram \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u043e \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u044f\u043c \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438 \u043a \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u043c\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e, \u043c\u044b \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0434\u043e\u043b\u0436\u043d\u044b \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u044c \u0441 \u043a\u043e\u0434\u043e\u043c \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0439 \u043b\u0435\u043d\u0442\u044b. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e:<\/p>\n<ul>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u044d\u043a\u0440\u0430\u043d, \u0433\u0434\u0435 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u043f\u043e\u0441\u0442\u044b;<\/p>\n<\/li>\n<li>\n<p>\u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c <strong>PictureViewer<\/strong> (hero \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438);<\/p>\n<\/li>\n<li>\n<p>\u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c <strong>AppBar<\/strong> (\u0432\u0435\u0440\u0445\u043d\u044e\u044e \u043f\u0430\u043d\u0435\u043b\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f) \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u0447\u0435\u043c \u043e\u0434\u043d\u0443 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044e \u0432 \u043f\u0440\u043e\u0444\u0438\u043b\u044c;<\/p>\n<\/li>\n<li>\n<p>\u0432\u043d\u0435\u0434\u0440\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0438 \u043e\u0442\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043b\u0435\u043d\u0442 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0420\u0430\u0437\u043e\u0431\u0440\u0430\u0432\u0448\u0438\u0441\u044c \u0441\u043e \u0432\u0441\u0435\u043c \u044d\u0442\u0438\u043c, \u0434\u0430\u043b\u0435\u0435, \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0445\u0440\u043e\u043d\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043b\u0435\u043d\u0442\u044b.<\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0440\u0430\u043d \u00ab\u041d\u043e\u0432\u0430\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f\u00bb<\/h2>\n<p>\u042d\u0442\u043e \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u044d\u043a\u0440\u0430\u043d, \u0433\u0434\u0435 \u0432\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442\u0435 \u043d\u043e\u0432\u043e\u0435 \u0444\u043e\u0442\u043e \u0432 \u0441\u0432\u043e\u0439 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043f\u043e\u0434\u043f\u0438\u0441\u044c\u044e \u043a \u043d\u0435\u043c\u0443.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b <code>components\/new_post\/new_post_screen.dart<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u043d\u0435\u0433\u043e:<\/p>\n<pre><code class=\"dart\">import 'dart:io'; import 'package:flutter\/material.dart'; import 'package:image_picker\/image_picker.dart'; import 'package:stream_feed_flutter_core\/stream_feed_flutter_core.dart'; import 'package:transparent_image\/transparent_image.dart';  import '..\/..\/app\/app.dart'; import '..\/app_widgets\/app_widgets.dart';  \/\/\/ Screen to choose photos and add a new feed post. class NewPostScreen extends StatefulWidget {   \/\/\/ Create a [NewPostScreen].   const NewPostScreen({Key? key}) : super(key: key);    \/\/\/ Material route to this screen.   static Route get route =>       MaterialPageRoute(builder: (_) => const NewPostScreen());    @override   _NewPostScreenState createState() => _NewPostScreenState(); }  class _NewPostScreenState extends State&lt;NewPostScreen> {   static const double maxImageHeight = 1000;   static const double maxImageWidth = 800;    final _formKey = GlobalKey&lt;FormState>();   final _text = TextEditingController();    XFile? _pickedFile;   bool loading = false;    final picker = ImagePicker();    Future&lt;void> _pickFile() async {     _pickedFile = await picker.pickImage(       source: ImageSource.gallery,       maxHeight: maxImageHeight,       maxWidth: maxImageWidth,       imageQuality: 70,     );     setState(() {});   }    Future&lt;void> _postImage() async {     if (_pickedFile == null) {       context.removeAndShowSnackbar('Please select an image first');       return;     }      if (!_formKey.currentState!.validate()) {       context.removeAndShowSnackbar('Please enter a caption');       return;     }     _setLoading(true);      final client = context.appState.client;      var decodedImage =         await decodeImageFromList(await _pickedFile!.readAsBytes());      final imageUrl =         await client.images.upload(AttachmentFile(path: _pickedFile!.path));      if (imageUrl != null) {       final _resizedUrl = await client.images.getResized(         imageUrl,         const Resize(300, 300),       );        if (_resizedUrl != null &amp;&amp; client.currentUser != null) {         await FeedProvider.of(context).bloc.onAddActivity(           feedGroup: 'user',           verb: 'post',           object: 'image',           data: {             'description': _text.text,             'image_url': imageUrl,             'resized_image_url': _resizedUrl,             'image_width': decodedImage.width,             'image_height': decodedImage.height,             'aspect_ratio': decodedImage.width \/ decodedImage.height           },         );       }     }      _setLoading(false, shouldCallSetState: false);     context.removeAndShowSnackbar('Post created!');      Navigator.of(context).pop();   }    void _setLoading(bool state, {bool shouldCallSetState = true}) {     if (loading != state) {       loading = state;       if (shouldCallSetState) {         setState(() {});       }     }   }    @override   void dispose() {     _text.dispose();     super.dispose();   }    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         leading: TapFadeIcon(           onTap: () => Navigator.pop(context),           icon: Icons.close,           iconColor: Theme.of(context).appBarTheme.iconTheme!.color!,         ),         actions: [           Padding(             padding: const EdgeInsets.all(8.0),             child: Center(               child: GestureDetector(                 onTap: _postImage,                 child: const Text('Share', style: AppTextStyle.textStyleAction),               ),             ),           )         ],       ),       body: loading           ? Center(               child: Column(                 mainAxisAlignment: MainAxisAlignment.center,                 children: const [                   CircularProgressIndicator(),                   SizedBox(height: 12),                   Text('Uploading...')                 ],               ),             )           : ListView(               children: [                 InkWell(                   onTap: _pickFile,                   child: SizedBox(                     height: 400,                     child: (_pickedFile != null)                         ? FadeInImage(                             fit: BoxFit.contain,                             placeholder: MemoryImage(kTransparentImage),                             image: Image.file(File(_pickedFile!.path)).image,                           )                         : Container(                             decoration: const BoxDecoration(                               gradient: LinearGradient(                                   begin: Alignment.bottomLeft,                                   end: Alignment.topRight,                                   colors: [                                     AppColors.bottomGradient,                                     AppColors.topGradient                                   ]),                             ),                             height: 300,                             child: const Center(                               child: Text(                                 'Tap to select an image',                                 style: TextStyle(                                   color: AppColors.light,                                   fontSize: 18,                                   shadows: &lt;Shadow>[                                     Shadow(                                       offset: Offset(2.0, 1.0),                                       blurRadius: 3.0,                                       color: Colors.black54,                                     ),                                     Shadow(                                       offset: Offset(1.0, 1.5),                                       blurRadius: 5.0,                                       color: Colors.black54,                                     ),                                   ],                                 ),                               ),                             ),                           ),                   ),                 ),                 const SizedBox(                   height: 22,                 ),                 Form(                   key: _formKey,                   child: Padding(                     padding: const EdgeInsets.all(8.0),                     child: TextFormField(                       controller: _text,                       decoration: const InputDecoration(          <\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-400465","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/400465","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=400465"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/400465\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=400465"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=400465"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=400465"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}