Розробка мобільного додатка для управління проектами
Мобільний task manager — це не "Jira у телефоні". Завдання з наради, швидкий статус від колеги в дорозі, нагадування про дедлайн за п'ять хвилин — мобільний контекст інший. Тому архітектура відрізняється: акцент на offline-first, мінімальне тертя введення даних та розумні push-сповіщення про критичні зміни.
Структура даних та синхронізація
Типова ієрархія: Workspace → Project → Board → Task → Subtask → Comment. На мобільному пристрої зберігається локальна копія (Room або Core Data), синхронізована з сервером.
Головна складність — конфлікти при офлайн-редагуванні. Двоє людей змінили статус завдання без інтернету. Рішення — Operational Transformation (використовується Notion, Google Docs) або простіший Last-Write-Wins з часовою міткою. Для більшості 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 дошка на мобільному
Drag-and-drop у мобільному Kanban — технічно складний момент. На Android — ItemTouchHelper для RecyclerView, але горизонтальний скролл між колонками + вертикальний скролл всередині колонки + перетягування через межу колонки — три конкуруючих жесту.
Робочий підхід: замість складного D&D — дотик на завдання → bottom sheet для вибору статусу. Менш красиво, але надійно працює на будь-якому пристрої.
// iOS — вибір статусу через контекстне меню (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 години — заплановане нагадування.
- Статус завдання змінено — тільки якщо користувач є творцем або спостерігачем.
// Серверна логіка визначення одержувачів сповіщення
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+) для відображення поточного таймера на екрані блокування.
// 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 дошка | 2–3 тижні |
| Офлайн-синхронізація | 2–3 тижні |
| Push-сповіщення | 1–2 тижні |
| Отримання часу | 1–2 тижні |
| Діаграма Ганта | 2–3 тижні |
MVP (завдання, Kanban, push, офлайн) — від 8 до 12 тижнів. Повний набір функцій включно з Gantt та отриманням часу — 16–20 тижнів.







