// SPDX-FileCopyrightText: 2026 FocusFlow contributors // SPDX-License-Identifier: AGPL-3.0-only /// Coordinates Today Screen Controller state for the FocusFlow Flutter UI. library; import 'package:flutter/foundation.dart'; import 'package:scheduler_core/scheduler_core.dart'; import '../models/today_screen_models.dart'; /// Reads Today state from the scheduler application layer. typedef TodayStateReader = Future> Function(); /// Base state for the compact Today screen. sealed class TodayScreenState { /// Creates a Today screen state. const TodayScreenState(); } /// Indicates that the Today screen is loading. class TodayScreenLoading extends TodayScreenState { /// Creates a loading Today screen state. const TodayScreenLoading(); } /// Indicates that the Today screen has renderable timeline data. class TodayScreenReady extends TodayScreenState { /// Creates a ready state with mapped screen [data]. const TodayScreenReady(this.data); /// Presentation model for the Today screen. final TodayScreenData data; } /// Indicates that the Today screen loaded successfully with no cards. class TodayScreenEmpty extends TodayScreenState { /// Creates an empty Today screen state. const TodayScreenEmpty(); } /// Indicates that the Today screen failed to load. class TodayScreenFailure extends TodayScreenState { /// Creates a failure state with a displayable [message]. const TodayScreenFailure(this.message); /// User-facing failure message or code. final String message; } /// Loads Today screen data and tracks selected timeline cards. class TodayScreenController extends ChangeNotifier { /// Creates a Today screen controller from a read callback. TodayScreenController({required this.read}); /// Callback used to read Today state. final TodayStateReader read; /// Private state stored as `_state` for this implementation. /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior. TodayScreenState _state = const TodayScreenLoading(); /// Private state stored as `_selectedCard` for this implementation. /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior. TimelineCardModel? _selectedCard; /// Current Today screen loading/data/failure state. TodayScreenState get state => _state; /// Currently selected timeline card, if a modal should be visible. TimelineCardModel? get selectedCard => _selectedCard; /// Loads Today state and maps it into presentation data. Future load() async { _state = const TodayScreenLoading(); notifyListeners(); try { final result = await read(); final failure = result.failure; if (failure != null) { _state = TodayScreenFailure(failure.detailCode ?? failure.code.name); notifyListeners(); return; } final data = TodayScreenData.fromTodayState(result.requireValue); _syncSelectedCard(data); _state = data.cards.isEmpty ? const TodayScreenEmpty() : TodayScreenReady(data); notifyListeners(); } on Object catch (error) { _state = TodayScreenFailure(error.toString()); notifyListeners(); } } /// Selects [card] when the card allows selection. void selectCard(TimelineCardModel card) { if (!card.isSelectable) { return; } _selectedCard = card; notifyListeners(); } /// Clears the selected timeline card. void clearSelection() { if (_selectedCard == null) { return; } _selectedCard = null; notifyListeners(); } /// Runs the `_syncSelectedCard` helper used inside this library. /// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read. void _syncSelectedCard(TodayScreenData data) { final selected = _selectedCard; if (selected == null) { return; } for (final card in data.cards) { if (card.id == selected.id) { _selectedCard = card; return; } } _selectedCard = null; } }