import 'dart:async'; import 'package:flutter/foundation.dart'; import 'api/support_api.dart'; import 'models/api_exception.dart'; import 'models/support_message.dart'; import 'models/support_ticket.dart'; enum TicketStatusFilter { open, closed, all } class InboxController extends ChangeNotifier { InboxController({required this.api}); final SupportApi api; TicketStatusFilter statusFilter = TicketStatusFilter.open; String? category; List tickets = []; bool loading = false; String? error; String? activeTicketId; SupportTicket? threadTicket; List messages = []; bool threadLoading = false; String? threadError; int _listGeneration = 0; int _threadGeneration = 0; String? get statusQuery { switch (statusFilter) { case TicketStatusFilter.open: return 'open'; case TicketStatusFilter.closed: return 'closed'; case TicketStatusFilter.all: return null; } } Future refreshList({bool showLoading = false}) async { final generation = ++_listGeneration; if (showLoading) { loading = true; error = null; notifyListeners(); } try { final next = await api.listTickets( status: statusQuery, category: category, ); if (generation != _listGeneration) { return; } tickets = next; error = null; } on ApiException catch (caught) { if (generation != _listGeneration || caught.isUnauthorized) { return; } error = caught.message; } finally { if (generation == _listGeneration) { loading = false; notifyListeners(); } } } Future refreshThread({bool showLoading = false}) async { final ticketId = activeTicketId; if (ticketId == null) { return; } final generation = ++_threadGeneration; if (showLoading) { threadLoading = true; threadError = null; notifyListeners(); } try { final thread = await api.getThread(ticketId); if (generation != _threadGeneration || activeTicketId != ticketId) { return; } _applyThread(thread); threadError = null; } on ApiException catch (caught) { if (generation != _threadGeneration || caught.isUnauthorized) { return; } threadError = caught.message; } finally { if (generation == _threadGeneration) { threadLoading = false; notifyListeners(); } } } Future sendReply(String raw) async { final ticketId = activeTicketId; if (ticketId == null) { return 'No ticket is open.'; } final validation = validateReplyBody(raw); if (validation != null) { return validation; } final generation = ++_threadGeneration; try { final thread = await api.reply(ticketId, raw.trim()); if (generation != _threadGeneration || activeTicketId != ticketId) { return null; } _applyThread(thread); threadError = null; notifyListeners(); return null; } on ApiException catch (caught) { if (caught.isUnauthorized) { return null; } return caught.message; } } Future setStatus(String status) async { final ticketId = activeTicketId; if (ticketId == null) { return 'No ticket is open.'; } final generation = ++_threadGeneration; try { final thread = await api.setStatus(ticketId, status); if (generation != _threadGeneration || activeTicketId != ticketId) { return null; } _applyThread(thread); threadError = null; notifyListeners(); return null; } on ApiException catch (caught) { if (caught.isUnauthorized) { return null; } return caught.message; } } void setStatusFilter(TicketStatusFilter value) { if (statusFilter == value) { return; } statusFilter = value; tickets = []; unawaited(refreshList(showLoading: true)); } void setCategory(String? value) { final next = (value == null || value.isEmpty) ? null : value; if (category == next) { return; } category = next; tickets = []; unawaited(refreshList(showLoading: true)); } void reset() { _listGeneration++; _threadGeneration++; statusFilter = TicketStatusFilter.open; category = null; tickets = []; loading = false; error = null; activeTicketId = null; threadTicket = null; messages = []; threadLoading = false; threadError = null; notifyListeners(); } void _applyThread(SupportThread thread) { threadTicket = thread.ticket; messages = thread.messages; final index = tickets.indexWhere((ticket) => ticket.id == thread.ticket.id); if (index >= 0) { tickets = [ for (var i = 0; i < tickets.length; i++) if (i == index) thread.ticket else tickets[i], ]; } } }