feat(app): add evaluation history, bot control panel, simulation visualizer, and vendored signalr_core
This commit is contained in:
@@ -1,21 +1,30 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:signalr_core/signalr_core.dart';
|
||||
import 'package:finlytic_app/core/network/api_client.dart';
|
||||
import 'package:finlytic_app/core/network/signalr_service.dart';
|
||||
import 'package:finlytic_app/features/news/models/news_article_model.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
class NewsRepository {
|
||||
final ApiClient apiClient;
|
||||
final String backendUrl;
|
||||
final FlutterSecureStorage secureStorage = const FlutterSecureStorage();
|
||||
|
||||
HubConnection? _hubConnection;
|
||||
final _liveNewsController = StreamController<NewsArticleModel>.broadcast();
|
||||
|
||||
Stream<NewsArticleModel> get liveNewsStream => _liveNewsController.stream;
|
||||
/// Central Real-Time WebSocket service (shared, authenticated `/hubs/news` connection).
|
||||
/// May be null for call-sites that only need REST access (e.g. one-off dialogs) and
|
||||
/// don't require the live feed; in that case [liveNewsStream] yields no events.
|
||||
final SignalRService? signalRService;
|
||||
|
||||
NewsRepository({required this.apiClient, required this.backendUrl});
|
||||
NewsRepository({
|
||||
required this.apiClient,
|
||||
required this.backendUrl,
|
||||
this.signalRService,
|
||||
});
|
||||
|
||||
/// Live news articles pushed by the central SignalRService's `/hubs/news` connection.
|
||||
/// The connection itself is established centrally (see `SignalRService.initSignalR()`),
|
||||
/// so this repository only maps the already-authenticated stream to typed models.
|
||||
Stream<NewsArticleModel> get liveNewsStream {
|
||||
final signalR = signalRService;
|
||||
if (signalR == null) return const Stream.empty();
|
||||
return signalR.newsArticleStream.map(NewsArticleModel.fromJson);
|
||||
}
|
||||
|
||||
Future<List<NewsArticleModel>> fetchNews({
|
||||
int page = 1,
|
||||
@@ -31,7 +40,7 @@ class NewsRepository {
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
};
|
||||
|
||||
|
||||
if (symbol != null && symbol.isNotEmpty) queryParams['symbol'] = symbol;
|
||||
if (isin != null && isin.isNotEmpty) queryParams['isin'] = isin;
|
||||
if (date != null && date.isNotEmpty) queryParams['date'] = date;
|
||||
@@ -66,55 +75,9 @@ class NewsRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> connectToLiveFeed() async {
|
||||
if (_hubConnection != null && _hubConnection!.state == HubConnectionState.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final token = await secureStorage.read(key: 'auth_token');
|
||||
final hubUrl = '$backendUrl/hubs/news${token != null ? '?access_token=$token' : ''}';
|
||||
|
||||
_hubConnection = HubConnectionBuilder()
|
||||
.withUrl(hubUrl, HttpConnectionOptions(
|
||||
logging: (level, message) => print(message),
|
||||
))
|
||||
.withAutomaticReconnect()
|
||||
.build();
|
||||
|
||||
_hubConnection!.on('ReceiveNewArticle', (arguments) {
|
||||
if (arguments != null && arguments.isNotEmpty) {
|
||||
try {
|
||||
// SignalR might send it as a Map or raw JSON depending on .NET format
|
||||
final dynamic rawPayload = arguments[0];
|
||||
final Map<String, dynamic> jsonData = rawPayload is String
|
||||
? jsonDecode(rawPayload)
|
||||
: Map<String, dynamic>.from(rawPayload);
|
||||
|
||||
final article = NewsArticleModel.fromJson(jsonData);
|
||||
_liveNewsController.add(article);
|
||||
} catch (e) {
|
||||
print('Error parsing live article: $e');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await _hubConnection!.start();
|
||||
print('Connected to News Live Feed');
|
||||
} catch (e) {
|
||||
print('Error connecting to News Live Feed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> disconnectFromLiveFeed() async {
|
||||
if (_hubConnection != null) {
|
||||
await _hubConnection!.stop();
|
||||
_hubConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_liveNewsController.close();
|
||||
disconnectFromLiveFeed();
|
||||
}
|
||||
/// No-op: the underlying `/hubs/news` WebSocket connection is owned and
|
||||
/// lifecycle-managed centrally by [SignalRService] (started once in
|
||||
/// `main.dart` after authentication), so this repository has nothing of
|
||||
/// its own to dispose.
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user