Минималистичное iOS-приложение для обмена моментами с друзьями в реальном времени.
Идея — фиксировать и делиться «текущим моментом» без перегрузки интерфейса.
- Регистрация и авторизация (sign up / sign in)
- Захват фото с реальной камеры (с fallback на demo-режим в симуляторе)
- Отправка моментов с подписью
- Просмотр полученных моментов с лентой и детальным экраном
- Ответ на момент (reply)
- Удаление своих моментов
- Поиск и добавление / удаление друзей
- Шаринг приглашения через Telegram, WhatsApp, Instagram, iMessage и другие приложения
- Профиль пользователя со статистикой (фото, друзья, стрик)
- Редактирование профиля
- Локальные push-уведомления о новых моментах
- Deep links (
currentmoment://,locketclone://) - Widget с последними моментами друзей (WidgetKit,
.systemMedium) - Сохранение фото в галерею
- Swift, UIKit, SwiftUI (Widget)
- Combine — реактивное состояние
- MVVM + Repository pattern
- WidgetKit
- AVFoundation — работа с камерой
- XCTest — unit-тесты
- Keychain — хранение JWT-токена (
AuthTokenStore)
- Vapor — Swift-фреймворк для серверной части
- Fluent + PostgreSQL — ORM и база данных
- REST API — HTTP-взаимодействие клиента с сервером
- JWT — аутентификация
Приложение построено на MVVM + Repository pattern с координатором навигации.
- AppCoordinator — управляет навигационным стеком, решает куда вести после авторизации
- AppDependencyContainer — собирает все зависимости в одном месте, передаёт во ViewModel через инициализатор
- View (UIKit / SwiftUI) — отображение UI, не знает об источнике данных
- ViewModel — бизнес-логика и состояние через
@Published+ Combine - CurrentMomentRepositoryProtocol — единственная точка входа в слой данных; реализация (
VaporCurrentMomentRepository) полностью заменяема на mock без изменений в ViewModel - APIClient — HTTP-клиент, общается с Vapor-бэкендом; токен хранится в Keychain
ViewModel
└── CurrentMomentRepositoryProtocol
└── VaporCurrentMomentRepository
└── APIClient (URLSession + Bearer JWT)
└── Vapor REST API → PostgreSQL
currentMoment/
├── App/
│ ├── AppCoordinator.swift
│ ├── AppDelegate.swift
│ ├── AppDependencyContainer.swift
│ ├── CurrentMomentRepositoryProtocol.swift
│ ├── LaunchViewController.swift
│ ├── SceneDelegate.swift
│ └── Services/
│ ├── CurrentMomentNotificationService.swift
│ ├── CurrentMomentWidgetService.swift
│ └── API/
│ ├── APIClient.swift
│ ├── APIFriendship.swift
│ ├── APIMoment.swift
│ ├── APIReply.swift
│ ├── APIUser.swift
│ ├── AuthTokenStore.swift
│ ├── Mappings.swift
│ └── VaporCurrentMomentRepository.swift
├── Core/
│ ├── DeepLinkRoute.swift
│ ├── Extensions/
│ │ ├── Date+Formatting.swift
│ │ ├── UIControl+Feedback.swift
│ │ └── UIView+Layout.swift
│ └── Infrastructure/
│ └── ImagePipeline.swift
├── DesignSystem/
│ ├── CMColor.swift
│ ├── CMTypography.swift
│ └── Components/
│ ├── AvatarView.swift
│ ├── CardContainerView.swift
│ ├── IconCircleButton.swift
│ ├── PrimaryButton.swift
│ └── ShutterButton.swift
├── Models/
│ ├── Friendship.swift
│ ├── Moment.swift
│ ├── User.swift
│ └── WidgetMomentSnapshot.swift
├── Modules/
│ ├── Auth/
│ │ ├── AuthViewController.swift
│ │ └── AuthViewModel.swift
│ ├── Camera/
│ │ ├── CameraCaptureMode.swift
│ │ ├── CameraPreviewView.swift
│ │ ├── CameraSessionController.swift
│ │ ├── CameraViewController.swift
│ │ ├── CameraViewModel.swift
│ │ ├── CapturedMomentAsset.swift
│ │ └── DemoCaptureImageFactory.swift
│ ├── Friends/
│ │ ├── FriendsViewController.swift
│ │ ├── FriendsViewModel.swift
│ │ └── FriendTableViewCell.swift
│ ├── History/
│ │ ├── HistoryViewController.swift
│ │ ├── HistoryViewModel.swift
│ │ ├── MomentDetailViewController.swift
│ │ └── MomentGridCell.swift
│ ├── Preview/
│ │ ├── PreviewRecipientCell.swift
│ │ ├── PreviewViewController.swift
│ │ └── PreviewViewModel.swift
│ └── Profile/
│ ├── EditProfileViewController.swift
│ ├── ProfileGridCell.swift
│ ├── ProfileHeaderView.swift
│ ├── ProfileViewController.swift
│ ├── ProfileViewModel.swift
│ ├── SettingRowView.swift
│ └── StatCardView.swift
├── Utils/
│ ├── AppError.swift
│ ├── UIColor+Hex.swift
│ └── UIState.swift
└── WidgetExtension/
├── CurrentMomentCircleWidget.swift
├── CurrentMomentWidgetBundle.swift
├── CurrentMomentWidgetStore.swift
└── WidgetMomentSnapshot.swift
Tests/
├── currentMomentTests/ # XCTest target
│ ├── CameraViewModelTests.swift
│ ├── CurrentMomentRepositoryTests.swift
│ ├── FriendsViewModelTests.swift
│ ├── PreviewViewModelTests.swift
│ ├── ProfileViewModelTests.swift
│ └── TestSupport.swift
└── Tests/ # дополнительный target
├── CurrentMomentRepositoryTests.swift
├── CurrentMomentTestSupport.swift
└── CurrentMomentViewModelTests.swift
Серверная часть написана на Vapor 4 (Swift) и использует PostgreSQL через Fluent ORM.
По умолчанию клиент ожидает сервер на http://localhost:8080. Для работы с реальным устройством в локальной сети измените baseURL в APIClient.swift.
| Метод | Путь | Описание |
|---|---|---|
| POST | /users/register |
Регистрация |
| POST | /users/login |
Авторизация, возвращает JWT |
| GET | /users/me |
Текущий пользователь |
| PATCH | /users/me |
Обновление профиля |
| GET | /users |
Список пользователей |
| GET | /moments |
Моменты текущего пользователя |
| POST | /moments |
Создать момент |
| DELETE | /moments/:id |
Удалить момент |
| GET | /friendships/:userId |
Список дружеских связей |
| POST | /replies |
Отправить ответ на момент |
| GET | /replies/:momentId |
Ответы на момент |
JWT-токен сохраняется в Keychain через AuthTokenStore и передаётся в заголовке Authorization: Bearer <token> при каждом запросе.
Unit-тесты покрывают основные сценарии работы с данными и бизнес-логикой:
CameraViewModelTests— захват фото в demo-режиме при недоступной камереCurrentMomentRepositoryTests— sign in, отправка момента, добавление / удаление другаFriendsViewModelTests— поиск пользователей, добавление в друзьяPreviewViewModelTests— отправка момента выбранным получателямProfileViewModelTests— статистика профиля отражает отправленные моменты
Используется XCTest с MockCurrentMomentRepository и TestCurrentMomentWidgetService в роли mock-зависимостей.
- Repository pattern — ViewModel зависит только от
CurrentMomentRepositoryProtocol, реализацию можно менять (mock / Vapor / другой бэкенд) без изменений в UI-слое - Vapor + PostgreSQL — типобезопасный Swift-бэкенд, единый язык на клиенте и сервере
- JWT в Keychain — токен хранится безопасно через
Security.framework, не в UserDefaults - Combine — единообразный реактивный поток данных от Repository до ViewModel
- DemoCaptureImageFactory — симулятор не имеет камеры; вместо краша генерируется красивый градиентный фрейм
- AppCoordinator — навигация вынесена из ViewController; каждый экран получает замыкания
onBack,onXxxбез прямых зависимостей
- Реализовать
sendMomentчерез загрузку изображения на сервер (сейчас заглушка) - Чат между друзьями
- Реализовать
searchUsersиaddFriend/removeFriendчерез API - Кэширование изображений на диск (сейчас только in-memory
NSCache) - Push-уведомления через APNs (сейчас только локальные)
- Улучшение обработки сетевых ошибок и retry-логика
- Виджет: загрузка реальных изображений (сейчас placeholder)
Al'bek Halapov
iOS Developer





