feat: add AI recommendations flow for tenders

Implement company AI onboarding/recommendation models, services, repository, and tender UI integration with supporting tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
AmirReza Jamali
2026-06-16 11:47:52 +03:30
parent 83b32549ce
commit 50e4f43738
34 changed files with 2558 additions and 112 deletions
+14
View File
@@ -3,6 +3,7 @@ import 'package:provider/single_child_widget.dart';
import 'package:tm_app/core/network/network_manager.dart';
import 'package:tm_app/core/services/tab_navigation_service.dart';
import 'package:tm_app/core/theme/theme_provider.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/repositories/liked_tenders_repository.dart';
import 'package:tm_app/data/repositories/notification_repository.dart';
@@ -13,6 +14,7 @@ import 'package:tm_app/data/services/home_service.dart';
import 'package:tm_app/data/services/liked_tenders_service.dart';
import 'package:tm_app/data/services/notification_check_service.dart';
import 'package:tm_app/data/services/notification_service.dart';
import 'package:tm_app/data/services/company_ai_service.dart';
import 'package:tm_app/data/services/notification_state_service.dart';
import 'package:tm_app/data/services/tender_detail_service.dart';
import 'package:tm_app/data/services/your_tenders_service.dart';
@@ -99,6 +101,10 @@ List<SingleChildWidget> get apiClients {
create: (context) => BoardService(networkManager: context.read()),
lazy: true,
),
Provider(
create: (context) => CompanyAiService(networkManager: context.read()),
lazy: true,
),
];
}
@@ -155,6 +161,14 @@ List<SingleChildWidget> get repositories {
create: (context) => BoardRepository(boardService: context.read()),
lazy: true,
),
Provider(
create:
(context) => CompanyAiRepository(
companyAiService: context.read(),
prefs: context.read(),
),
lazy: true,
),
];
}
+9
View File
@@ -9,4 +9,13 @@ class PrefKeys {
static const String bearer = 'bearer';
static const String refreshToken = 'refresh_token';
static const String customerData = 'customer_data';
/// Fingerprint (hash of the company's document ids + website) of the last
/// profile version we triggered AI onboarding for. Lets us skip redundant
/// `POST /api/v1/onboarding` calls until the company's data actually changes.
static const String onboardingFingerprint = 'ai_onboarding_fingerprint';
/// Epoch milliseconds when AI onboarding was last started. Used by the
/// Recommended tab to show a "still learning" hint for a short grace period.
static const String onboardingStartedAt = 'ai_onboarding_started_at';
}
+2
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../data/repositories/auth_repository.dart';
import '../../data/repositories/company_ai_repository.dart';
import '../../data/repositories/profile_repository.dart';
import '../../view_models/profile_view_model.dart';
@@ -13,6 +14,7 @@ Widget profileProvider({required Widget child}) {
(context) => ProfileViewModel(
profileRepository: context.read<ProfileRepository>(),
authRepository: context.read<AuthRepository>(),
companyAiRepository: context.read<CompanyAiRepository>(),
),
child: child,
);
+23 -7
View File
@@ -1,17 +1,33 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../data/repositories/company_ai_repository.dart';
import '../../data/repositories/home_repository.dart';
import '../../data/repositories/tenders_repository.dart';
import '../../view_models/ai_recommendations_view_model.dart';
import '../../view_models/tenders_view_model.dart';
/// Lazy TendersViewModel provider
/// Wraps screens that need TendersViewModel
/// Lazy providers for the tenders section.
///
/// Provides [TendersViewModel] (the "All" tab) and [AiRecommendationsViewModel]
/// (the "Recommended" AI tab) to screens that need them.
Widget tendersProvider({required Widget child}) {
return ChangeNotifierProvider(
create:
(context) => TendersViewModel(
tendersRepository: context.read<TendersRepository>(),
),
return MultiProvider(
providers: [
ChangeNotifierProvider(
create:
(context) => TendersViewModel(
tendersRepository: context.read<TendersRepository>(),
),
),
ChangeNotifierProvider(
create:
(context) => AiRecommendationsViewModel(
companyAiRepository: context.read<CompanyAiRepository>(),
homeRepository: context.read<HomeRepository>(),
),
),
],
child: child,
);
}