Skip to content

Licoler/Current-moment

Repository files navigation

Current Moment

Минималистичное iOS-приложение для обмена моментами с друзьями в реальном времени.
Идея — фиксировать и делиться «текущим моментом» без перегрузки интерфейса.


Features

  • Регистрация и авторизация (sign up / sign in)
  • Захват фото с реальной камеры (с fallback на demo-режим в симуляторе)
  • Отправка моментов с подписью
  • Просмотр полученных моментов с лентой и детальным экраном
  • Ответ на момент (reply)
  • Удаление своих моментов
  • Поиск и добавление / удаление друзей
  • Шаринг приглашения через Telegram, WhatsApp, Instagram, iMessage и другие приложения
  • Профиль пользователя со статистикой (фото, друзья, стрик)
  • Редактирование профиля
  • Локальные push-уведомления о новых моментах
  • Deep links (currentmoment://, locketclone://)
  • Widget с последними моментами друзей (WidgetKit, .systemMedium)
  • Сохранение фото в галерею

Tech Stack

iOS Client

  • Swift, UIKit, SwiftUI (Widget)
  • Combine — реактивное состояние
  • MVVM + Repository pattern
  • WidgetKit
  • AVFoundation — работа с камерой
  • XCTest — unit-тесты
  • Keychain — хранение JWT-токена (AuthTokenStore)

Backend

  • Vapor — Swift-фреймворк для серверной части
  • Fluent + PostgreSQL — ORM и база данных
  • REST API — HTTP-взаимодействие клиента с сервером
  • JWT — аутентификация

Architecture

Приложение построено на 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

Project Structure

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

Backend (Vapor + PostgreSQL)

Серверная часть написана на Vapor 4 (Swift) и использует PostgreSQL через Fluent ORM.

По умолчанию клиент ожидает сервер на http://localhost:8080. Для работы с реальным устройством в локальной сети измените baseURL в APIClient.swift.

REST API (основные эндпоинты)

Метод Путь Описание
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> при каждом запросе.


Testing

Unit-тесты покрывают основные сценарии работы с данными и бизнес-логикой:

  • CameraViewModelTests — захват фото в demo-режиме при недоступной камере
  • CurrentMomentRepositoryTests — sign in, отправка момента, добавление / удаление друга
  • FriendsViewModelTests — поиск пользователей, добавление в друзья
  • PreviewViewModelTests — отправка момента выбранным получателям
  • ProfileViewModelTests — статистика профиля отражает отправленные моменты

Используется XCTest с MockCurrentMomentRepository и TestCurrentMomentWidgetService в роли mock-зависимостей.


Screenshots


Key Decisions

  • Repository pattern — ViewModel зависит только от CurrentMomentRepositoryProtocol, реализацию можно менять (mock / Vapor / другой бэкенд) без изменений в UI-слое
  • Vapor + PostgreSQL — типобезопасный Swift-бэкенд, единый язык на клиенте и сервере
  • JWT в Keychain — токен хранится безопасно через Security.framework, не в UserDefaults
  • Combine — единообразный реактивный поток данных от Repository до ViewModel
  • DemoCaptureImageFactory — симулятор не имеет камеры; вместо краша генерируется красивый градиентный фрейм
  • AppCoordinator — навигация вынесена из ViewController; каждый экран получает замыкания onBack, onXxx без прямых зависимостей

TODO

  • Реализовать sendMoment через загрузку изображения на сервер (сейчас заглушка)
  • Чат между друзьями
  • Реализовать searchUsers и addFriend / removeFriend через API
  • Кэширование изображений на диск (сейчас только in-memory NSCache)
  • Push-уведомления через APNs (сейчас только локальные)
  • Улучшение обработки сетевых ошибок и retry-логика
  • Виджет: загрузка реальных изображений (сейчас placeholder)

Author

Al'bek Halapov
iOS Developer

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages