feat(App): update Finlytic Flutter app UI and blocs

This commit is contained in:
2026-08-09 21:01:46 +02:00
parent e7427b7464
commit a708d2977c
591 changed files with 1095105 additions and 0 deletions
@@ -0,0 +1,40 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:finlytic_app/features/asset_detail/repositories/asset_repository.dart';
import 'asset_detail_event.dart';
import 'asset_detail_state.dart';
class AssetDetailBloc extends Bloc<AssetDetailEvent, AssetDetailState> {
final AssetRepository repository;
AssetDetailBloc({required this.repository}) : super(AssetDetailInitial()) {
on<LoadAssetData>(_onLoadAssetData);
on<ForceRefreshAssetData>(_onForceRefreshAssetData);
}
Future<void> _onLoadAssetData(LoadAssetData event, Emitter<AssetDetailState> emit) async {
emit(AssetDetailLoading());
try {
final results = await Future.wait([
repository.getFundamentalData(event.symbol),
repository.getTechnicalAnalysis(event.symbol),
]);
emit(AssetDetailLoaded(
fundamentalData: results[0] as dynamic,
technicalAnalysis: results[1] as dynamic,
));
} catch (e) {
emit(AssetDetailError("Fehler beim Laden der Asset-Daten."));
}
}
Future<void> _onForceRefreshAssetData(ForceRefreshAssetData event, Emitter<AssetDetailState> emit) async {
try {
await repository.forceRefreshFundamentalData(event.symbol);
// Optional: re-load after a delay, or rely on MQTT/SignalR to push the new data.
} catch (e) {
// ignore
}
}
}
@@ -0,0 +1,26 @@
import 'package:equatable/equatable.dart';
abstract class AssetDetailEvent extends Equatable {
const AssetDetailEvent();
@override
List<Object?> get props => [];
}
class LoadAssetData extends AssetDetailEvent {
final String symbol;
const LoadAssetData(this.symbol);
@override
List<Object?> get props => [symbol];
}
class ForceRefreshAssetData extends AssetDetailEvent {
final String symbol;
const ForceRefreshAssetData(this.symbol);
@override
List<Object?> get props => [symbol];
}
@@ -0,0 +1,33 @@
import 'package:equatable/equatable.dart';
import 'package:finlytic_app/features/asset_detail/models/fundamental_data_model.dart';
import 'package:finlytic_app/features/asset_detail/models/technical_analysis_model.dart';
abstract class AssetDetailState extends Equatable {
const AssetDetailState();
@override
List<Object?> get props => [];
}
class AssetDetailInitial extends AssetDetailState {}
class AssetDetailLoading extends AssetDetailState {}
class AssetDetailLoaded extends AssetDetailState {
final FundamentalDataModel? fundamentalData;
final TechnicalAnalysisModel? technicalAnalysis;
const AssetDetailLoaded({this.fundamentalData, this.technicalAnalysis});
@override
List<Object?> get props => [fundamentalData, technicalAnalysis];
}
class AssetDetailError extends AssetDetailState {
final String message;
const AssetDetailError(this.message);
@override
List<Object?> get props => [message];
}
@@ -0,0 +1,19 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'asset_fundamentals_event.dart';
import 'asset_fundamentals_state.dart';
import '../../repositories/asset_repository.dart';
class AssetFundamentalsBloc extends Bloc<AssetFundamentalsEvent, AssetFundamentalsState> {
final AssetRepository repository;
AssetFundamentalsBloc({required this.repository}) : super(AssetFundamentalsInitial()) {
on<LoadAssetFundamentals>((event, emit) async {
emit(AssetFundamentalsLoading());
try {
final data = await repository.getAssetFundamentals(event.isin, event.forceRefresh, ticker: event.ticker);
emit(AssetFundamentalsLoaded(data));
} catch (e) {
emit(AssetFundamentalsError(e.toString()));
}
});
}
}
@@ -0,0 +1,7 @@
abstract class AssetFundamentalsEvent {}
class LoadAssetFundamentals extends AssetFundamentalsEvent {
final String isin;
final bool forceRefresh;
final String? ticker;
LoadAssetFundamentals(this.isin, {this.forceRefresh = false, this.ticker});
}
@@ -0,0 +1,13 @@
import '../../models/fundamental_data_model.dart';
abstract class AssetFundamentalsState {}
class AssetFundamentalsInitial extends AssetFundamentalsState {}
class AssetFundamentalsLoading extends AssetFundamentalsState {}
class AssetFundamentalsLoaded extends AssetFundamentalsState {
final FundamentalDataModel? data;
AssetFundamentalsLoaded(this.data);
}
class AssetFundamentalsError extends AssetFundamentalsState {
final String message;
AssetFundamentalsError(this.message);
}
@@ -0,0 +1,20 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'asset_header_event.dart';
import 'asset_header_state.dart';
import '../../repositories/asset_repository.dart';
class AssetHeaderBloc extends Bloc<AssetHeaderEvent, AssetHeaderState> {
final AssetRepository repository;
AssetHeaderBloc({required this.repository}) : super(AssetHeaderInitial()) {
on<LoadAssetHeader>((event, emit) async {
final prevData = state is AssetHeaderLoaded ? (state as AssetHeaderLoaded).data : (state is AssetHeaderLoading ? (state as AssetHeaderLoading).previousData : null);
emit(AssetHeaderLoading(previousData: prevData));
try {
final data = await repository.getAssetHeader(event.isin, exchange: event.exchange, ticker: event.ticker);
emit(AssetHeaderLoaded(data));
} catch (e) {
emit(AssetHeaderError(e.toString()));
}
});
}
}
@@ -0,0 +1,8 @@
abstract class AssetHeaderEvent {}
class LoadAssetHeader extends AssetHeaderEvent {
final String isin;
final bool forceRefresh;
final String? exchange;
final String? ticker;
LoadAssetHeader(this.isin, {this.forceRefresh = false, this.exchange, this.ticker});
}
@@ -0,0 +1,16 @@
import '../../models/asset_model.dart';
abstract class AssetHeaderState {}
class AssetHeaderInitial extends AssetHeaderState {}
class AssetHeaderLoading extends AssetHeaderState {
final AssetModel? previousData;
AssetHeaderLoading({this.previousData});
}
class AssetHeaderLoaded extends AssetHeaderState {
final AssetModel? data;
AssetHeaderLoaded(this.data);
}
class AssetHeaderError extends AssetHeaderState {
final String message;
AssetHeaderError(this.message);
}
@@ -0,0 +1,19 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'asset_technical_event.dart';
import 'asset_technical_state.dart';
import '../../repositories/asset_repository.dart';
class AssetTechnicalBloc extends Bloc<AssetTechnicalEvent, AssetTechnicalState> {
final AssetRepository repository;
AssetTechnicalBloc({required this.repository}) : super(AssetTechnicalInitial()) {
on<LoadAssetTechnical>((event, emit) async {
emit(AssetTechnicalLoading());
try {
final data = await repository.getAssetTechnical(event.isin, event.forceRefresh, ticker: event.ticker);
emit(AssetTechnicalLoaded(data));
} catch (e) {
emit(AssetTechnicalError(e.toString()));
}
});
}
}
@@ -0,0 +1,7 @@
abstract class AssetTechnicalEvent {}
class LoadAssetTechnical extends AssetTechnicalEvent {
final String isin;
final bool forceRefresh;
final String? ticker;
LoadAssetTechnical(this.isin, {this.forceRefresh = false, this.ticker});
}
@@ -0,0 +1,13 @@
import '../../models/technical_analysis_model.dart';
abstract class AssetTechnicalState {}
class AssetTechnicalInitial extends AssetTechnicalState {}
class AssetTechnicalLoading extends AssetTechnicalState {}
class AssetTechnicalLoaded extends AssetTechnicalState {
final TechnicalAnalysisModel? data;
AssetTechnicalLoaded(this.data);
}
class AssetTechnicalError extends AssetTechnicalState {
final String message;
AssetTechnicalError(this.message);
}
@@ -0,0 +1,52 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'asset_trades_event.dart';
import 'asset_trades_state.dart';
import '../../repositories/asset_repository.dart';
class AssetTradesBloc extends Bloc<AssetTradesEvent, AssetTradesState> {
final AssetRepository repository;
AssetTradesBloc({required this.repository}) : super(AssetTradesInitial()) {
on<LoadAssetTrades>((event, emit) async {
emit(AssetTradesLoading());
try {
final data = await repository.getAssetTrades(event.isin, event.status);
emit(AssetTradesLoaded(data));
} catch (e) {
emit(AssetTradesError(e.toString()));
}
});
on<TriggerManualAnalysis>((event, emit) async {
try {
await repository.triggerManualAnalysis(event.isin, payload: event.payload);
add(LoadAssetTrades(event.isin));
} catch (e) {
emit(AssetTradesError("Failed to trigger manual analysis: $e"));
}
});
on<RejectTradeEvent>((event, emit) async {
try {
await repository.rejectTrade(event.tradeId);
add(LoadAssetTrades(event.isin));
} catch (e) {
emit(AssetTradesError("Failed to reject trade: $e"));
}
});
on<AcceptTradeEvent>((event, emit) async {
try {
await repository.acceptTrade(event.tradeAcceptanceDto);
add(LoadAssetTrades(event.isin));
} catch (e) {
emit(AssetTradesError("Failed to accept trade: $e"));
}
});
on<CloseTradeEvent>((event, emit) async {
try {
await repository.closeTrade(event.tradeId, event.exitPrice);
add(LoadAssetTrades(event.isin));
} catch (e) {
emit(AssetTradesError("Failed to close trade: $e"));
}
});
}
}
@@ -0,0 +1,31 @@
import 'package:finlytic_app/features/trades/models/trade_acceptance_dto.dart';
import 'package:finlytic_app/features/asset_detail/models/manual_analysis_request_dto.dart';
abstract class AssetTradesEvent {}
class LoadAssetTrades extends AssetTradesEvent {
final String isin;
final String? status;
LoadAssetTrades(this.isin, {this.status});
}
class TriggerManualAnalysis extends AssetTradesEvent {
final String isin;
final ManualAnalysisRequestDto? payload;
TriggerManualAnalysis(this.isin, {this.payload});
}
class RejectTradeEvent extends AssetTradesEvent {
final String tradeId;
final String isin;
RejectTradeEvent(this.tradeId, this.isin);
}
class AcceptTradeEvent extends AssetTradesEvent {
final TradeAcceptanceDto tradeAcceptanceDto;
final String isin;
AcceptTradeEvent(this.tradeAcceptanceDto, this.isin);
}
class CloseTradeEvent extends AssetTradesEvent {
final String tradeId;
final String isin;
final double exitPrice;
CloseTradeEvent(this.tradeId, this.isin, this.exitPrice);
}
@@ -0,0 +1,13 @@
import '../../../trades/models/trade_model.dart';
abstract class AssetTradesState {}
class AssetTradesInitial extends AssetTradesState {}
class AssetTradesLoading extends AssetTradesState {}
class AssetTradesLoaded extends AssetTradesState {
final List<TradeModel> data;
AssetTradesLoaded(this.data);
}
class AssetTradesError extends AssetTradesState {
final String message;
AssetTradesError(this.message);
}