Демо-проект

Приложение записи в барбершоп на живом API

Flutter-приложение, которое ходит в настоящий сервер: прайс, свободное время и сама запись приезжают по сети, а не из заглушек.

мобильное приложение и клиентское API · Flutter · Dart · REST API · Go

5 экрановот прайса до переноса записи
8 услугпрайс и цены приходят с сервера
3 шагаот выбора услуги до записи
7 видов сбояразобраны в коде, на экране - фразы
iOS и Androidодна кодовая база, две системы
Первый экран демо: Приложение записи в барбершоп на живом API

Задача

BROMID - барбершоп в Екатеринбурге: четыре мастера, восемь позиций прайса, работа только по записи. Демо-проект: студия, мастера, цены и записи вымышлены, задача - настоящая и типовая. Заказчику мобильного приложения обычно показывают красивые экраны на выдуманных данных, и отличить работающее приложение от макета, который умеет только листаться, он не может.

Поэтому у демо ровно одна цель: приложение, которое разговаривает с настоящим сервером. Ни прайса, ни расписания в коде приложения нет: услуги, цены, длительности, свободное время и сами записи приходят по сети.

Решение

Пять экранов и три шага до записи: выбрать услугу, выбрать мастера и время, подтвердить. Дальше карточка «Вы записаны» - услуга, время, мастер, стоимость, адрес студии, кнопки «Перенести» и «Отменить запись».

  • Прайс свёрстан как меню: название, точечный заполнитель, цена, под названием длительность. Восемь позиций приходят из каталога сервера вместе с ценами и минутами.
  • Свободное время зависит от выбранной услуги, потому что длительность считает сервер: переключение со «Стрижки» на «Комплекс» - с 45 минут на 90 - заметно прореживает сетку.
  • Первая плитка в ленте мастеров - «Любой мастер, кто раньше освободится»: гость чаще выбирает время, а не человека, и свободного мастера подбирает сервер.
  • Запись создаётся, переносится и отменяется по-настоящему: семь методов API, от открытия сессии до переноса. Отмена спрашивает подтверждение отдельным диалогом, а не срабатывает с первого касания.
  • Клиентское API написано под это приложение: свой префикс /api/app, свой токен, ответы без единого чужого имени и телефона. Дашбордное API студии приложению закрыто намеренно: в нём лежат контакты всех клиентов, а им в кадре не место.

Детали, которые легко не заметить

  • Время показывается в часовом поясе студии, а не телефона. Сервер отдаёт метку со смещением, приложение читает смещение из неё же: гость из Москвы увидит екатеринбургские 14:30, а не свои 12:30.
  • Слот могут занять между открытием экрана и нажатием кнопки. Сервер отвечает отказом, приложение говорит «Это время уже заняли. Выберите другое.» и меняет кнопку на «Выбрать другое время», возвращая к обновлённой сетке.
  • Семь видов сбоя разведены в коде: обрыв сети, таймаут в 12 секунд, устаревшая сессия, занятое время, пропавшая запись, отказ сервера, ответ не по схеме. Текст зависит ещё и от того, что гость делал: пропавшая связь на прайсе - «Не получилось связаться со студией», она же в момент записи - «Связь со студией пропала. Запись не создана».
  • Ни кода ответа, ни технической строки на экран не выходит: ответ 500 приложение показывает как «Студия не отвечает. Попробуйте позже.»
  • Устаревшая сессия не выбрасывает гостя в начало сценария: появляется кнопка «Повторить», по ней приложение открывает новую сессию и повторяет то же действие - на экране подтверждения это та же самая запись.
  • Пока едет прайс, вместо спиннера на весь экран стоит скелет из восьми строк, ровно по числу позиций прайса: экран не прыгает в момент загрузки.
  • Число визитов до бесплатной стрижки склоняется по-русски: «2 визита», «5 визитов». Мелочь, отсутствие которой видно сразу.
  • Демо-гигиена: строка дисклеймера внизу прайса и отдельный экран «О приложении» - кадр нельзя принять за настоящий сервис студии.
  • Шрифты лежат в репозитории, а не тянутся из сети. Сверх самого Flutter в рантайме у приложения одна зависимость - HTTP-клиент. Библиотеки состояния нет: экраны переключают явные состояния «ожидание - загрузка - данные - отказ».

Границы демо

Объём демо - готовность к съёмке, а не к магазину приложений: живой ссылки не будет, в App Store и Google Play приложение не публикуется. Веб-версии тоже нет: адрес сервера приложение выбирает через dart:io, которого в браузере не существует. Интерфейс только русский, локализации нет. Серия кадров снята на iOS, на Android пока один контрольный экран. Системный масштаб шрифта в демо зафиксирован, чтобы кадры совпадали между запусками, - в приложении для магазина так делать нельзя.