83b77c05ef
- Updated the handling of card movement results to differentiate between successful and unsuccessful API responses. - Implemented rollback of board state and error messaging for failed moves, ensuring a more robust user experience. - Enhanced the notification system to surface relevant error messages when card movements fail.
128 lines
4.0 KiB
Dart
128 lines
4.0 KiB
Dart
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<BoardColumn> get columns => _board?.columns ?? const [];
|
|
bool get hasData => _board != null && columns.isNotEmpty;
|
|
|
|
Future<void> getBoard() async {
|
|
_isLoading = true;
|
|
_errorMessage = null;
|
|
notifyListeners();
|
|
|
|
final result = await _boardRepository.getBoard();
|
|
|
|
switch (result) {
|
|
case Ok<BoardResponse>():
|
|
_board = result.value.data?.board;
|
|
if (_board == null) {
|
|
_errorMessage = result.value.error?.message ?? 'No board data';
|
|
}
|
|
break;
|
|
case Error<BoardResponse>():
|
|
_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<Result<MoveCardResponse>> 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<BoardCard>.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<MoveCardResponse>():
|
|
// 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<MoveCardResponse>():
|
|
_board = _board!.copyWith(columns: previousColumns);
|
|
notifyListeners();
|
|
return result;
|
|
}
|
|
}
|
|
}
|