import 'package:flutter/material.dart'; import '../core/utils/result.dart'; import '../data/repositories/board_repositories.dart'; import '../data/services/board_service.dart'; import '../data/services/model/board_response/board_response.dart'; class BoardViewModel with ChangeNotifier { final BoardRepository _boardRepository; BoardViewModel({required BoardRepository boardRepository}) : _boardRepository = boardRepository; bool _isLoading = false; String? _errorMessage; Board? _board; bool get isLoading => _isLoading; String? get errorMessage => _errorMessage; Board? get board => _board; List get columns => _board?.columns ?? const []; bool get hasData => _board != null && columns.isNotEmpty; Future getBoard() async { _isLoading = true; _errorMessage = null; notifyListeners(); final result = await _boardRepository.getBoard(); switch (result) { case Ok(): _board = result.value.data?.board; if (_board == null) { _errorMessage = result.value.error?.message ?? 'No board data'; } break; case Error(): _errorMessage = result.error.toString(); break; } _isLoading = false; notifyListeners(); } /// Look up the column a given card currently lives in. BoardColumn? columnForCard(String cardId) { for (final column in columns) { if (column.cards.any((c) => c.id == cardId)) return column; } return null; } /// Move a card to [targetColumnId] at the end of that column. /// Optimistically updates local state, then calls the API; rolls back on /// failure. Future> moveCard({ required BoardCard card, required BoardColumn targetColumn, }) async { final cardId = card.id; final targetColumnId = targetColumn.id; if (cardId == null || targetColumnId == null || _board == null) { return Result.error(Exception('Invalid card or column')); } final sourceColumn = columnForCard(cardId); if (sourceColumn == null) { return Result.error(Exception('Card not found in any column')); } if (sourceColumn.id == targetColumnId) { return Result.ok(MoveCardResponse(success: true, message: 'No change')); } final previousColumns = _board!.columns; final newOrder = targetColumn.cards.length; _board = _board!.copyWith( columns: previousColumns.map((column) { if (column.id == sourceColumn.id) { final remaining = column.cards.where((c) => c.id != cardId).toList(); return column.copyWith( cards: remaining, cardCount: remaining.length, ); } if (column.id == targetColumnId) { final updated = List.from(column.cards) ..add(card.copyWith(columnId: targetColumnId, order: newOrder)); return column.copyWith( cards: updated, cardCount: updated.length, ); } return column; }).toList(), ); notifyListeners(); final result = await _boardRepository.moveCard( cardId: cardId, columnId: targetColumnId, newOrder: newOrder, ); switch (result) { case Ok(): // HTTP 200 alone isn't success: the API can return // {"success": false, "message": ...} (e.g. column over-limit, // permission denied). Only keep the optimistic move when the body // confirms success; otherwise roll back and surface the message. if (result.value.success == true) { return result; } _board = _board!.copyWith(columns: previousColumns); notifyListeners(); return Result.error( Exception(result.value.message ?? 'Failed to move card'), ); case Error(): _board = _board!.copyWith(columns: previousColumns); notifyListeners(); return result; } } }