import 'package:flutter/foundation.dart'; import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/data/repositories/company_ai_repository.dart'; import 'package:tm_app/data/repositories/home_repository.dart'; import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; /// Drives the "Recommended" tab in the tenders section. /// /// Calls `GET /api/v1/tenders/recommend`. When the result is empty shortly /// after onboarding it surfaces a "still learning" hint instead of an empty /// error. class AiRecommendationsViewModel with ChangeNotifier { AiRecommendationsViewModel({ required CompanyAiRepository companyAiRepository, required HomeRepository homeRepository, }) : _companyAiRepository = companyAiRepository, _homeRepository = homeRepository; final CompanyAiRepository _companyAiRepository; final HomeRepository _homeRepository; /// How long after onboarding an empty result is treated as "still learning". static const Duration _stillLearningWindow = Duration(minutes: 30); static const int _pageSize = 20; bool _isLoading = false; String? _errorMessage; List _recommendedTenders = const []; bool _stillLearning = false; bool _hasLoaded = false; bool get isLoading => _isLoading; String? get errorMessage => _errorMessage; List get recommendedTenders => _recommendedTenders; bool get stillLearning => _stillLearning; /// True once a load has completed (used to distinguish "no results" from /// "not loaded yet"). bool get hasLoaded => _hasLoaded; /// True when there is nothing to show and we're not loading or erroring. bool get isEmpty => !_isLoading && _errorMessage == null && _recommendedTenders.isEmpty; /// Loads recommendations if they haven't been loaded yet this session. Future loadIfNeeded() async { if (_hasLoaded || _isLoading) { return; } await loadRecommendations(); } /// Loads recommended tenders for the Recommended tab. Future loadRecommendations() async { _isLoading = true; _errorMessage = null; _stillLearning = false; notifyListeners(); final result = await _homeRepository.getHomeRecommendedTenders( limit: _pageSize, offset: 0, ); var loadedSuccessfully = false; switch (result) { case Ok(): _recommendedTenders = result.value.data?.tenders.whereType().toList() ?? const []; if (_recommendedTenders.isEmpty) { _stillLearning = _onboardingWasRecent(); } loadedSuccessfully = true; case Error(): _recommendedTenders = const []; _errorMessage = result.error.toString(); } _isLoading = false; _hasLoaded = loadedSuccessfully; notifyListeners(); } /// Pull-to-refresh: always re-fetches. Future refresh() => loadRecommendations(); bool _onboardingWasRecent() { final startedAt = _companyAiRepository.lastOnboardingStartedAt(); if (startedAt == null) { return false; } final elapsed = DateTime.now().millisecondsSinceEpoch - startedAt; return elapsed >= 0 && elapsed <= _stillLearningWindow.inMilliseconds; } }