Разработка мобильного приложения для управления проектами
Мобильный task manager — это не «Jira в телефоне». Задачи из встречи, быстрый статус от коллеги в дороге, уведомление о дедлайне через пять минут — мобильный контекст другой. Поэтому архитектура отличается: акцент на offline-first, минимальный input friction и умные push-уведомления о критических изменениях.
Структура данных и синхронизация
Типичная иерархия: Workspace → Project → Board → Task → Subtask → Comment. На мобиле хранится локальная копия (Room или Core Data), синхронизируемая с сервером.
Главная сложность — конфликты при офлайн-редактировании. Два человека поменяли статус задачи пока были без сети. Решение — Operational Transformation (используют Notion, Google Docs) или более простой Last-Write-Wins с timestamp. Для большинства task manager'ов Last-Write-Wins достаточно.
// Android — офлайн-операция с последующей синхронизацией
data class PendingOperation(
val id: String = UUID.randomUUID().toString(),
val type: OperationType,
val entityId: String,
val payload: String, // JSON
val createdAt: Long = System.currentTimeMillis(),
var syncedAt: Long? = null
)
class TaskRepository(
private val taskDao: TaskDao,
private val pendingOpsDao: PendingOperationDao,
private val api: ProjectApi
) {
suspend fun updateTaskStatus(taskId: String, newStatus: TaskStatus) {
// Обновляем локально мгновенно
taskDao.updateStatus(taskId, newStatus)
// Ставим в очередь синхронизации
val op = PendingOperation(
type = OperationType.UPDATE_TASK_STATUS,
entityId = taskId,
payload = json.encodeToString(mapOf("status" to newStatus.name))
)
pendingOpsDao.insert(op)
// Пробуем синхронизировать если есть сеть
if (networkMonitor.isConnected) syncPendingOperations()
}
}
Kanban board на мобиле
Drag-and-drop в мобильном Kanban — технически сложный момент. На Android — ItemTouchHelper для RecyclerView, но горизонтальный скролл между колонками + вертикальный скролл внутри колонки + drag через границу колонки — три конкурирующих gesture recognizer.
Рабочий подход: вместо сложного D&D — tap на задачу → bottom sheet с выбором статуса. Менее красиво, но надёжно работает на любом устройстве.
// iOS — выбор статуса через context menu (iOS 13+)
func tableView(_ tableView: UITableView,
contextMenuConfigurationForRowAt indexPath: IndexPath,
point: CGPoint) -> UIContextMenuConfiguration? {
let task = tasks[indexPath.row]
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
let moveActions = TaskStatus.allCases.map { status in
UIAction(title: status.title, image: status.icon) { [weak self] _ in
self?.viewModel.updateStatus(task: task, to: status)
}
}
return UIMenu(title: "Переместить в...", children: moveActions)
}
}
Push-уведомления: что действительно нужно
Не «уведомление о каждом изменении» — это раздражает. Умная нотификационная система учитывает контекст:
- @упоминание в комментарии — немедленно, высокий приоритет.
- Назначена задача — немедленно.
- Изменён дедлайн задачи — немедленно назначенному исполнителю.
- Дедлайн через 24 часа — scheduled reminder.
- Статус задачи изменён — только если пользователь создатель или наблюдатель.
// Серверная логика определения получателей уведомления
fun getNotificationRecipients(event: TaskEvent): Set<UserId> {
return buildSet {
when (event) {
is TaskEvent.Mentioned -> add(event.mentionedUserId)
is TaskEvent.Assigned -> add(event.assigneeId)
is TaskEvent.StatusChanged -> {
addAll(task.watchers)
add(task.creatorId)
// Не уведомляем того, кто сам поменял статус
remove(event.changedBy)
}
}
}
}
На клиенте — настройки уведомлений по типам, хранятся в профиле пользователя на сервере. Так при переключении устройств настройки не теряются.
Тайм-трекинг
Простой тайм-трекер: старт/стоп таймер привязан к задаче. На мобиле — foreground service (Android) или Background Task + live activity (iOS 16+) для отображения текущего таймера на lock screen.
// iOS 16+ Live Activity для таймера задачи
struct TimerActivityAttributes: ActivityAttributes {
struct ContentState: Codable, Hashable {
var elapsedSeconds: Int
var taskName: String
}
var taskId: String
}
// Запуск
let state = TimerActivityAttributes.ContentState(elapsedSeconds: 0, taskName: task.name)
let activity = try Activity<TimerActivityAttributes>.request(
attributes: TimerActivityAttributes(taskId: task.id),
contentState: state,
pushType: nil
)
Диаграмма Ганта
Диаграмма Ганта на мобиле — читаемость при горизонтальном скролле и зуме. Кастомный Canvas/UIView с рендерингом временной шкалы. Готовые библиотеки: DHTMLX Gantt для WebView-подхода или кастомный рендер через SurfaceView (Android) / CALayer (iOS).
WebView-подход проще в реализации, но хуже по производительности при 200+ задачах. Нативный рендер — сложнее, но плавнее.
Сроки
| Функциональность | Трудоёмкость |
|---|---|
| Список задач + фильтры + детальная карточка | 3–4 нед |
| Kanban board | 2–3 нед |
| Офлайн-синхронизация | 2–3 нед |
| Push-уведомления | 1–2 нед |
| Тайм-трекинг | 1–2 нед |
| Диаграмма Ганта | 2–3 нед |
MVP (задачи, Kanban, push, офлайн) — от 8 до 12 недель. Полный feature set включая Gantt и тайм-трекинг — 16–20 недель.







