Привет! Продолжаю выкладывать перевод статьи, которую я использовал как основу для реализации социального функционала в нашем проекте Dom24x7, где люди могут общаться друг с другом, решать возникающие бытовые проблемы, а также взаимодействовать с УК/ТСЖ. Первую часть статьи можно прочитать тут. Наконец, мы добрались до самой интригующей части статьи. Перед тем как продолжить, давайте быстро обсудим подход, с которым мы подойдем к созданию нашего клона. Наш Stream-agram будет состоять из нескольких основных компонентов UI, от которых мы будем отталкиваться позже в статье. Это такие компоненты как: Экран входа (mock-объект): там, где мы входим в свой профиль; Главный экран: откуда мы сможем переходить в другие области нашего приложения (по умолчанию там будет открыта хронологическая лента); Саму страницу с хронологической лентой (домашняя вкладка): место, где пользователь смотрит посты тех на кого подписан; Вкладку личного профиля: там, где представлена вся информация о профиле, включая опубликованные посты; Вкладку поиска: откуда пользователь может подписываться и отписываться от других пользователей; Экран «Редактировать профиль»: там, где пользователь может изменить основную информацию профиля и основное фото; Экран «Новая публикация»: там, где пользователь может выкладывать новые посты (действия) в свой профиль. Мы начнем с упрощенных версий этих вкладок и страниц и будем вносить в них изменения шаг за шагом, по мере того как будем добавлять больше функций в наше приложений. Конечным результатом станет практически идентичный клон Instagram*. Каждое приложение является уникальным и потребует своей собственной структуры папок. Ту структуру, которую мы используем в данной статье, ни в коем случае нельзя считать лучше какой-либо другой. Это лишь один из удобных способов того, как можно организовать код для конкретно нашего приложения. Это приложение группирует весь код под две папки верхнего уровня: app и components. В components находится все, что будет представлено в пользовательском интерфейсе - его компоненты. Другими словами это будут кусочки приложения, которые смогут работать независимо от других его частей; например - страницы/вкладки, кнопки, и виджеты. Каталог component будет также содержать папку с именем app_widgets, которая будет содержать все общие виджеты, используемые в приложении. Каталог app содержит классы, относящиеся ко всему приложению, а также код, который будет использован множеством компонентов. Например, app state и navigation. Для того, чтобы аккуратно организовать импортируемые элементы, в данной инструкции мы используем файлы Barrel (далее просто - баррель-файлы). Баррель-файлы позволяют свернуть импортируемые элементы из нескольких папок в один удобный файл. К концу статьи структура ваших папок и файлов должна иметь похожий вид. Вы можете либо создать всё это сейчас самостоятельно, либо позже по мере прохождения статьи. ⚠️ Заметьте, что баррель-файлы помечены звездочкой «*» Как уже было упомянуто выше, в целях демонстрации, в данной статье мы захардкодим наших пользователей, то есть заполним данные для них вручную. Создайте файл app/state/demo_users.dart и добавьте в него следующее: В данном файле присутствуют несколько TODO комментариев, поскольку нам нужно добавить String токены, созданные ранее. Обязательно добавьте правильные токены для нужных пользователей. Если вы используете другие имена пользователей, то вам также понадобится изменить соответствующие строки в данном файле. Код выше содержит в себе enum класс DemoAppUser c расширением DemoAppUserX. Сделано это для того, чтобы мы могли получить доступ к другим данным (например к токенам или к именам пользователей). Этот файл не является критично важным, и вы можете создать ваших фейковых пользователей любым другим способом. Создайте баррель-файл командой app/state/state.dart и экспортируйте его: Далее, добавьте баррель-файл уровня выше, который имеет вид app/app.dart экспортируйте его: Мы собираемся использовать те же цвета и шрифты, которые вы видите при использовании настоящего Инстаграма, для того чтобы наше приложение давало пользователям те же эмоции, при использовании. Мы также хотим отметить, что в нашем клоне будет присутствовать дневной ☀️ и ночной 🌚 режимы. Основный режим будет зависеть от настроек платформы. В приложении не будет реализована смена режимов в зависимости от дневного времени, поскольку это слегка выходит за рамки основной цели нашей статьи. Создайте app/theme.dart и добавьте следующее: Вы можете не торопиться и изучить этот код в своем собственном темпе, или подробно остановиться на нем позже, когда мы будет непосредственно использовать некоторые из терминов, содержащихся в нем. В баррель-файле state/state.dart добавьте следующие строки: Можете ли вы поверить в то, что в таком огромном приложении нам понадобится создать с нуля всего лишь один класс модели? 😱 Вы можете в этом не сомневаться, поскольку большинство моделей, которые нам понадобятся будут взяты из стандартных пакетов Stream Feed. Создайте app/state/models/user.dart и добавьте следующее: Это будет вашим пользовательским классом данным (User Data Class). Он хранит в себе ряд полей и предоставляет такие удобные методы как: toMap, fromMap, и copyWith. Они пригодятся нам позже. Мы создаем этот класс по одной причине - чтобы мы могли легко создавать объекты из дополнительных данных, которые будут хранится для каждого пользователя Stream Feed. Хоть у нас всего и одна модель, мы не будем подавать плохой пример и все равно создадим наш баррель-файл. Создайте app/state/models/models.dart и добавьте следующее: В баррель-файле app/state/state.dart добавьте следующие строки: Предмет вечных споров. Для нашего инстаграм клона нам понадобится управлять только базовым состоянием приложения так, чтобы мы могли легко вносить изменения в наш UI. Большая часть состояния будет управляться с помощью пакета stream_feed_flutter_core package. Однако нам все равно необходим легкий способ чтобы передавать состояние в нашем приложении (например - авторизованных пользователей). В этой статье мы не пытаемся отстаивать какую-либо точку зрения по поводу управления состоянием. Порой, простой виджет типа Inherited - все что вам нужно; а благодаря Provider виджеты такого типа становятся максимально простыми в использовании. К тому же, Provider также предоставляет множество полезных функций, с которыми мы познакомимся позже в статье. Создайте файл app/state/app_state.dart и добавьте в него следующее: Данный класс не слишком сложный. Далее в статье, мы еще вернемся к нему и дополним код. На данный момент он только управляет локальными пользователям и их данными, которые поступают в него. Класс под названием ChangeNotifier помогает с легкостью управлять состоянием во Flutter. Таким образом, вы храните изменяемые переменные в классе, который сможете обновить в будущем. Как только файл будет обновлен, вы сможете уведомить любых ваших слушателей (listeners) путем вызова метода notifyListeners. Как вы можете увидеть в данном классе, переменная с названием _streamagramUser изначально является пустой и будет задана как только вы вызовете метод connect, требующий класс DemoAppUser. Здесь важно обратить внимание на класс StreamFeedClient. Он включает множество методов, по которым он взаимодействует с API Stream Feeds. Одним из таких методов является метод setUser. Данный метод локально устанавливает текущих пользователей Stream Feeds для приложения и, далее, обрабатывает их. Если пользователь не существует, он создает его, а если пользователь все таки есть, он считывает его данные с бэкенда Stream Feeds. Метод setUser требует наличия объекта User, который включает в себя: уникальный идентификатор пользователя (его имя пользователя); дополнительные данные о пользователе, которые в дальнейшем будут использованы на сервере (имя, URL адрес фото главного профиля, и так далее, в зависимости от ваших предпочтений); токен аутентификации. Данный метод входит в класс Future (асинхронный возвращаемый объект). Как только класс переходит в состояние завершен, вы обновляете объект _streamagramUser путем обращения к fromMap, который создает новый объект в каталоге Map. После этого обновляете всех слушателей путем вызова notifyListeners. Обновите баррель-файл app/state/state.dart: Создайте новый файл. Назовите его app/stream_agram.dart и добавьте в него следующее: В данном файле, мы реализуем несколько важных моментов: Инициализируем StreamFeedClient при помощи вашего ключа доступа (API Key); Создаем класс AppState, который содержит StreamFeedClient; Создаем FeedBloc (бизнес-логика для ваших приложений в Stream Feeds). Этот класс также можно найти в пакете Stream Feeds, и мы будем часто возвращаться к нему позже в данной статье; Обертываем MaterialApp при помощи ChangeNotifierProvider открывая доступ к объекту AppState. Благодаря этому, все приложение сможет с легкостью получать доступ к состоянию.  Использование factory-метода .value важно, поскольку он предоставляет доступ к уже созданному объекту appState; Присваиваем значение LoginScreen  аргументу home. Это понадобится нам при создании экрана входа в скором времени; Используем аргумент Builder, чтобы обернуть наше приложение с помощью FeedProvider, что в свою очередь предоставляет доступ к объекту feedBloc для всего приложения. FeedProvider необходим виджетам Stream Feeds в целях получения доступа к FeedBloc независимо от того где они находятся в дереве виджетов. Измените ваш баррель-файл app/app.dart следующим образом: Далее, создайте файл app/utils.dart, который будет содержать несколько полезных расширений, которые мы часто будем использовать позже в статье. Эти расширения сделают некоторые операции проще в нашем UI и помогут сократить количество кода, необходимое нам для выполнения часто повторяющихся операций. Эти расширения позволят вам: убирать и выводить на показ виджет Snackbar вместе с сообщением; получать доступ к AppState из BuildContext. Добавьте в файл следующее: Теперь наконец пришло время изменить наш баррель-файл app/app.dart в последний раз 🥳 (по крайней мере, пока что). Добавьте: Далее, вам необходимо будем создать экран входа - моковый экран, в котором вы сможете залогиниться под одним из ваших фейковых пользователей. Создайте файл components/login/login_screen.d и добавьте в него следующее: Вы можете остановиться на этом файле и поподробнее изучить его самостоятельно. Кратко, этот виджет выполняет следующее: Выводит на показ кнопки, показывающие имя каждого пользователя. Если нажать на имя - будет предпринята попытка авторизации; Вызывает метод connect в AppState и если ответ положительный (true), происходит переход на HomeScreen (главный экран); в противном случае Snackbar выдаст ошибку; Показывает сообщения Snackbar о состоянии подключения. Как только вы запустите приложение, ваш экран будет иметь следующий вид: Не забудьте создать ваш баррель-файл для авторизации components/login/login.dart: Это будет основным экраном вашего приложения, откуда пользователи смогут переходить на другие экраны и вкладки. Открывая настоящий Instagram*, мы имеем возможность переходить на другие вкладки приложения при помощи навигационной панели внизу экрана. Мы говорим про такие вкладки как: Вкладка с хронологической лентой или домашняя вкладка (показывается по умолчанию при авторизации); Вкладка поиска; Вкладка Reels; Вкладка «Магазин»; Вкладка профиля пользователя. В оригинальном приложении, у вас также есть возможность быстро перейти к наиболее часто используемым функциям с помощью верхней панели. Это такие функции как: Создать новый пост;  Просмотреть обновления; Открыть сообщения. На данном этапе мы создадим упрощенную версию приложения. Позже, мы изменим код, чтобы внести некоторые улучшения в работу главного экрана и его функциональность. Для начала мы воспользуемся PageView и BottomNavigationBar для того, чтобы навигация в нашем приложении была похожа на настоящий Instagram и имела следующие вкладки: Хронологическую ленту; Вкладку поиска; Вкладку профиля пользователя. Пока что, все эти страницы будут содержать только текст-заглушку, который мы изменим позже. Мы также создадим класс AppBar (это будет верхней панелью нашего приложения), который временно будет содержать только title (название). Позже мы добавим сюда больше элементов управления. Создайте файл components/home/home_screen.dart и добавьте в него следующее: Давайте подробнее разберем эту часть кода: эта часть кода разделена на три отдельные части: AppBar, PageView и BottomNavigationBar; список виджетов хранится в переменной _homePages. На данном этапе, они представляют собой лишь текст-заглушку, созданный виджетом Text; в коде также присутствует PageController для перехода между страницами PageView. Он используется в  _StreamagramBottomNavBar. Здесь также есть слушатель, добавленный к контроллеру, чтобы обновлять UI, когда тот меняется; значение для физики PageView устанавливается как  NeverScrollableScrollPhysics. Благодаря этому, единственным способом перемещаться между вкладками является нажатие на одну из них в нижней навигационной панели; здесь, в классе AppBar, мы также используем шрифт grandHotel из пакета GoogleFonts для того, чтобы шрифт названия нашего приложения был идентичен шрифту в настоящем Instagram. Это название отображается в левой верхней части экрана. Теперь если вы запустите приложение, то главный экран будет выглядеть следующим образом: И вы также должны иметь возможность перемещаться между разными страницами, благодаря PageView. Последний штрих. Удалите все из main.dart и вставьте следующее: Теперь, если вы все сделали правильно, у вас должен быть экран входа, где вы можете выбрать пользователя и перейти на главный экран после авторизации. Если что-то пошло не так, проверьте правильно ли вы указали токены пользователей, их идентификаторы или ключ API. Супер 🚀. Теперь, когда мы завершили работу над базовыми блоками, мы можем перейти к более сложным. По ходу создания вашего инстаграм клона, вы будете использовать несколько глобальных компонентов - виджетов. Сейчас, давайте добавим один из них. Это будет виджет Avatar, который показывает главное фото профиля (в случае если оно установлено), или просто инициалы пользователя (в случае когда фото отсутствует). Создайте components/app_widgets/avatars.dart  и добавьте следующий код: Вы можете самостоятельно изучить этот виджет. Важным моментом здесь является то, что он поддерживает несколько разных конструкторов, названия которых соответствуют их размерах (к примеру - tiny, large, huge и т.д.). Данный виджет будет использоваться всякий раз, когда вы будете выводить на показ главное фото пользователя (его аватар). Данный виджет  также использует пакет cached_network_image package для кэширования изображений. Создайте баррель-файл components/app_widgets/app_widgets.dart и экспортируйте следующий файл: Как минимум, вкладка профиля для нашего приложения должна содержать следующее: главное фото и информацию о пользователе; информацию о подписках и подписчиках; функционал позволяющий легко редактировать профиль; ленту постов, созданных пользователем; возможность публиковать новые посты. Далее, мы детально разберем каждый из этих пунктов. Создайте файл components/profile/profile_page.dart и добавьте в него: В этом файле мы наконец используем некоторые виджеты из пакета stream_chat_flutter_core, в целях отображения действий в ленте. Вы можете изучить эту часть кода самостоятельно в удобном для вас темпе. Сейчас давайте обсудим наиболее важные его части: FlatFeedCore требует, чтобы вы указали feedGroup. Для нашей страницы профиля нам нужно указать здесь «user». Не забывайте, что это именно то значение, что мы указали в самом начале статьи; FlatFeedCore имеет четыре билдера, которые нужны нам для обработки различных состояний (loadingBuilder, errorBuilder, emptyBuilder, и feedBuilder); emptyBuilder возвращает CustomScrollView, который отображает такие виджеты как: _ProfileHeader, _EditProfileButton, и _NoPostsMessage; feedBuilder вы обновите позже; виджет _ProfileHeader использует Provider для того, чтобы следить за состоянием AppState; в этой части кода вы также можете увидеть несколько TODO комментариев, требующих обработки, но к ним мы вернемся чуть позже. Благодаря виджету FlatFeedCore извлечение данных об отдельных постах (действий) из вашей ленты становиться проще, и вы можете отображать их как пожелаете. Существует ряд аргументов, которые вы можете присвоить этому виджету в целях улучшения пагинации и фильтрования содержимого в вашей ленте. Однако пока что, мы не будем глубоко углубляться в это. Вы можете изучить этот вопрос после прочтения данной статьи. Создайте баррель-файл components/profile/profile.dart и добавьте туда следующее: Затем вернитесь обратно к components/home/home_screen.dart и измените переменную _homePages, чтобы она показывала ProfilePage. Это должно выглядеть следующим образом: Если вы сейчас запустите приложение и откроете вкладку профиля, то ее вид должен быть похожим на это: Экран «Редактировать профиль» (Edit Profile) отображает информацию о пользователе (например его имя) и позволяет пользователям изменять главное фото профиля. Конечно, вы можете и расширить функционал этого экрана, добавив в него ряд других функций если пожелаете, но на данный момент мы хотим просто отобразить главное фото пользователя, его обычное имя и пользовательское. Перед тем как перейти к созданию UI, вам нужно обновить класс AppState. Откройте app/state/app_state.dart и измените его как указано ниже: Код выше создает переменную состояния isUploadingProfilePicture, для которой изначально задано значение false. Метод updateProfilePhoto выполняет следующее: Считывает путь к файлу изображения; Меняет значение переменной isUploadingProfilePicture на true. Вы сможете увидеть эту переменную в вашем UI; Загружает файл изображения на Stream CDN, при помощи StreamFeedClient и AttachmentFile; Используя метод getResized, создает уменьшенную версию изображения, которую будет использовать приложение, когда для фото профиля выбран размер tiny или small; Добавляет новый URL вашего фото в _streamagramUser; Снова меняет значение переменной isUploadingProfilePicture на false; Обновляет данные пользователя в базе данных вызовом метода update для currentUser. Этот код также вызывает метод notifyListeners несколько раз для того, чтобы обновить UI. Для того, внедрить в наш клон функцию, позволяющую выбирать изображение из галереи нашего смартфона (например для публикации или в качестве главного фото профиля), мы будем использовать пакет image_picker. По ссылке вы можете найти рекомендации по установке. В зависимости от операционной системы (iOS или Android), шаги могут различаться. Создайте файл components/profile/edit_profile_screen.dart и добавьте следующее: Давайте поподробнее остановимся на этой части кода: здесь мы используем собственный PageRouteBuilder, который использует SlideTransition. Это позволяет странице плавно сворачиваться после того как вы её закрываете, подобно тому как это сделано в Instagram; также используем различные UI-виджеты для того, чтобы отображать информацию о пользователе; метод _changePicture мы уже создали ранее в статье. Этот метод использует пакет image_picker для выбора изображения из галереи и последующей загрузки его при помощи  метода updateProfilePhoto; здесь мы также, указываем максимальный размер и разрешение загружаемого изображения с помощью maxWidth, maxHeight, и imageQuality. Теперь ваш экран «Редактировать профиль» должен принять следующий вид: Измените баррель-файл components/profile/profile.dart  следующим образом: Для файла components/profile/profile_page.dart  измените TODO комментарий в виджете _EditProfileButton Ура!!! 🥳 Если вы правильно выполнили все шаги ,описанные выше, теперь на странице вашего профиля вы должны иметь возможность поставить реальное изображение на аватар вместо заглушки в виде инициалов, как на примере ниже: На сегодня все. Постараюсь последнюю третью часть перевода выложить в ближайшее время. Третью часть статьи читайте тут. Instagram* - запрещенная в России социальная сеть. Также предлагаю подписаться на мой канал и на канал моего проекта:Ссылка на канал проекта в телеграм: https://t.me/dom24x7Ссылка на мой канал: https://t.me/evgaj