refactor: save current workspace state including FinlyticAnalyzer fixes, FinlyticApp trade route alignment, and DTO audit documentation

This commit is contained in:
2026-08-12 18:30:42 +02:00
parent a9553e9fbf
commit 3d8af3940b
163 changed files with 3421 additions and 1751 deletions
@@ -17,15 +17,19 @@ import '../tabs/technical_tab.dart';
import '../tabs/trades_tab.dart';
class AssetPageDesktopLayout extends StatefulWidget {
final String symbol;
final String isin;
final String? name;
final String? selectedTicker;
const AssetPageDesktopLayout({super.key, required this.symbol});
const AssetPageDesktopLayout(
{super.key, required this.isin, this.selectedTicker, this.name});
@override
State<AssetPageDesktopLayout> createState() => _AssetPageDesktopLayoutState();
}
class _AssetPageDesktopLayoutState extends State<AssetPageDesktopLayout> with SingleTickerProviderStateMixin {
class _AssetPageDesktopLayoutState extends State<AssetPageDesktopLayout>
with SingleTickerProviderStateMixin {
late TabController _tabController;
String? _selectedExchange;
String? _selectedTicker;
@@ -47,21 +51,29 @@ class _AssetPageDesktopLayoutState extends State<AssetPageDesktopLayout> with Si
_selectedExchange = newExchange;
_selectedTicker = newTicker;
});
context.read<AssetHeaderBloc>().add(LoadAssetHeader(widget.symbol, exchange: newExchange, ticker: newTicker));
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(widget.symbol, ticker: newTicker, forceRefresh: false));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.symbol, ticker: newTicker, forceRefresh: false));
context.read<AssetHeaderBloc>().add(
LoadAssetHeader(widget.isin, exchange: newExchange, ticker: newTicker));
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(widget.isin,
ticker: newTicker, forceRefresh: false));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.isin,
ticker: newTicker, forceRefresh: false));
final favCubit = context.read<FavoritesCubit>();
if (favCubit.state.isFavorite(widget.symbol)) {
favCubit.updateFavoriteTicker(widget.symbol, newTicker);
if (favCubit.state.isFavorite(widget.isin)) {
favCubit.updateFavoriteTicker(widget.isin, newTicker);
}
}
void _handleForceRefresh() {
context.read<AssetHeaderBloc>().add(LoadAssetHeader(widget.symbol, forceRefresh: true, exchange: _selectedExchange, ticker: _selectedTicker));
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(widget.symbol, ticker: _selectedTicker, forceRefresh: true));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.symbol, ticker: _selectedTicker, forceRefresh: true));
context.read<AssetTradesBloc>().add(LoadAssetTrades(widget.symbol));
context.read<AssetHeaderBloc>().add(LoadAssetHeader(widget.isin,
forceRefresh: true,
exchange: _selectedExchange,
ticker: _selectedTicker));
// AssetFundamentalsBloc is omitted here because AssetHeaderBloc already triggers forceRefresh=true
// for fundamentals, and the listener below will fetch the updated data with forceRefresh=false.
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.isin,
ticker: _selectedTicker, forceRefresh: true));
context.read<AssetTradesBloc>().add(LoadAssetTrades(widget.isin));
}
@override
@@ -73,91 +85,108 @@ class _AssetPageDesktopLayoutState extends State<AssetPageDesktopLayout> with Si
if (state is AssetHeaderLoaded && state.data != null) {
if (_selectedTicker == null) {
setState(() {
_selectedTicker = state.data!.symbol;
_selectedExchange = state.data!.exchange;
_selectedTicker = widget.selectedTicker;
//_selectedExchange = state.data!.exchange;
});
// Re-trigger fundamentals and TA with resolved ticker
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(widget.symbol, ticker: state.data!.symbol, forceRefresh: false));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.symbol, ticker: state.data!.symbol, forceRefresh: false));
}
// Re-trigger fundamentals and TA with resolved ticker whenever header loads (e.g. after force refresh)
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(
widget.isin,
ticker: _selectedTicker,
forceRefresh: false));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(
widget.isin,
ticker: _selectedTicker,
forceRefresh: false));
}
},
child: LayoutBuilder(
builder: (context, constraints) {
final height = constraints.maxHeight.isFinite ? constraints.maxHeight : MediaQuery.of(context).size.height;
return SizedBox(
height: height,
width: double.infinity,
child: Column(
children: [
AssetHeroHeader(
symbol: widget.symbol,
selectedExchange: _selectedExchange,
onExchangeChanged: _handleExchangeChanged,
onForceRefresh: _handleForceRefresh,
),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Left Panel (Chart Focus)
Expanded(
flex: 5,
child: Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: theme.cardSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: theme.glassBorder),
final height = constraints.maxHeight.isFinite
? constraints.maxHeight
: MediaQuery.of(context).size.height;
return SizedBox(
height: height,
width: double.infinity,
child: Column(
children: [
AssetHeroHeader(
isin: widget.isin,
name: widget.name ?? widget.isin,
symbol: _selectedTicker ?? widget.selectedTicker,
onExchangeChanged: _handleExchangeChanged,
onForceRefresh: _handleForceRefresh,
),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Left Panel (Chart Focus)
Expanded(
flex: 5,
child: Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: theme.cardSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: theme.glassBorder),
),
child: TechnicalTab(
isin: widget.isin,
symbol: _selectedTicker,
isDesktopLeftPanel: true),
),
child: TechnicalTab(symbol: _selectedTicker ?? widget.symbol, isDesktopLeftPanel: true),
),
),
// Right Panel (Tabs for fundamentals/trades)
Expanded(
flex: 3,
child: Container(
margin: const EdgeInsets.only(top: 16, right: 16, bottom: 16),
decoration: BoxDecoration(
color: theme.cardSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: theme.glassBorder),
),
child: Column(
children: [
TabBar(
controller: _tabController,
labelColor: theme.primaryColor,
unselectedLabelColor: theme.textMuted,
indicatorColor: theme.primaryColor,
dividerColor: theme.glassBorder,
labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
tabs: const [
Tab(text: 'OVERVIEW'),
Tab(text: 'TRADES'),
],
),
Expanded(
child: TabBarView(
// Right Panel (Tabs for fundamentals/trades)
Expanded(
flex: 3,
child: Container(
margin: const EdgeInsets.only(
top: 16, right: 16, bottom: 16),
decoration: BoxDecoration(
color: theme.cardSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: theme.glassBorder),
),
child: Column(
children: [
TabBar(
controller: _tabController,
children: [
FundamentalsTab(symbol: _selectedTicker ?? widget.symbol),
TradesTab(symbol: widget.symbol),
labelColor: theme.primaryColor,
unselectedLabelColor: theme.textMuted,
indicatorColor: theme.primaryColor,
dividerColor: theme.glassBorder,
labelStyle: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 13),
tabs: const [
Tab(text: 'OVERVIEW'),
Tab(text: 'TRADES'),
],
),
),
],
Expanded(
child: TabBarView(
controller: _tabController,
children: [
FundamentalsTab(
isin: widget.isin,
symbol: _selectedTicker,
),
TradesTab(symbol: widget.isin),
],
),
),
],
),
),
),
),
],
],
),
),
),
],
),
);
},
),
],
),
);
},
),
);
}
}
@@ -17,22 +17,26 @@ import '../tabs/technical_tab.dart';
import '../tabs/trades_tab.dart';
class AssetPageMobileLayout extends StatefulWidget {
final String symbol;
final String isin;
final String? name;
final String? selectedTicker;
const AssetPageMobileLayout({super.key, required this.symbol});
const AssetPageMobileLayout(
{super.key, required this.isin, this.selectedTicker, this.name});
@override
State<AssetPageMobileLayout> createState() => _AssetPageMobileLayoutState();
}
class _AssetPageMobileLayoutState extends State<AssetPageMobileLayout> with SingleTickerProviderStateMixin {
class _AssetPageMobileLayoutState extends State<AssetPageMobileLayout>
with SingleTickerProviderStateMixin {
late TabController _tabController;
String? _selectedExchange;
String? _selectedTicker;
@override
void initState() {
super.initState();
//_selectedTicker = widget.selectedTicker;
_tabController = TabController(length: 3, vsync: this);
}
@@ -44,24 +48,30 @@ class _AssetPageMobileLayoutState extends State<AssetPageMobileLayout> with Sing
void _handleExchangeChanged(String newExchange, String newTicker) {
setState(() {
_selectedExchange = newExchange;
//_selectedExchange = newExchange;
_selectedTicker = newTicker;
});
context.read<AssetHeaderBloc>().add(LoadAssetHeader(widget.symbol, exchange: newExchange, ticker: newTicker));
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(widget.symbol, ticker: newTicker, forceRefresh: false));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.symbol, ticker: newTicker, forceRefresh: false));
context.read<AssetHeaderBloc>().add(
LoadAssetHeader(widget.isin, exchange: newExchange, ticker: newTicker));
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(widget.isin,
ticker: newTicker, forceRefresh: false));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.isin,
ticker: newTicker, forceRefresh: false));
final favCubit = context.read<FavoritesCubit>();
if (favCubit.state.isFavorite(widget.symbol)) {
favCubit.updateFavoriteTicker(widget.symbol, newTicker);
if (favCubit.state.isFavorite(widget.isin)) {
favCubit.updateFavoriteTicker(widget.isin, newTicker);
}
}
void _handleForceRefresh() {
context.read<AssetHeaderBloc>().add(LoadAssetHeader(widget.symbol, forceRefresh: true, exchange: _selectedExchange, ticker: _selectedTicker));
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(widget.symbol, ticker: _selectedTicker, forceRefresh: true));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.symbol, ticker: _selectedTicker, forceRefresh: true));
context.read<AssetTradesBloc>().add(LoadAssetTrades(widget.symbol));
context.read<AssetHeaderBloc>().add(LoadAssetHeader(widget.isin,
forceRefresh: true, ticker: _selectedTicker));
// AssetFundamentalsBloc is omitted here because AssetHeaderBloc already triggers forceRefresh=true
// for fundamentals, and the listener below will fetch the updated data with forceRefresh=false.
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.isin,
ticker: _selectedTicker, forceRefresh: true));
context.read<AssetTradesBloc>().add(LoadAssetTrades(widget.isin));
}
@override
@@ -73,56 +83,70 @@ class _AssetPageMobileLayoutState extends State<AssetPageMobileLayout> with Sing
if (state is AssetHeaderLoaded && state.data != null) {
if (_selectedTicker == null) {
setState(() {
_selectedTicker = state.data!.symbol;
_selectedExchange = state.data!.exchange;
_selectedTicker = widget.selectedTicker;
//_selectedExchange = state.data!.exchange;
});
// Re-trigger fundamentals and TA with resolved ticker
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(widget.symbol, ticker: state.data!.symbol, forceRefresh: false));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(widget.symbol, ticker: state.data!.symbol, forceRefresh: false));
}
// Re-trigger fundamentals and TA with resolved ticker whenever header loads (e.g. after force refresh)
context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(
widget.isin,
ticker: _selectedTicker,
forceRefresh: false));
context.read<AssetTechnicalBloc>().add(LoadAssetTechnical(
widget.isin,
ticker: _selectedTicker,
forceRefresh: false));
}
},
child: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverToBoxAdapter(
child: AssetHeroHeader(
symbol: widget.symbol,
selectedExchange: _selectedExchange,
onExchangeChanged: _handleExchangeChanged,
onForceRefresh: _handleForceRefresh,
),
),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
TabBar(
controller: _tabController,
labelColor: theme.primaryColor,
unselectedLabelColor: theme.textMuted,
indicatorColor: theme.primaryColor,
dividerColor: Colors.transparent,
labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
tabs: const [
Tab(text: 'OVERVIEW'),
Tab(text: 'TECHNICAL'),
Tab(text: 'TRADES'),
],
return [
SliverToBoxAdapter(
child: AssetHeroHeader(
isin: widget.isin,
name: widget.name ?? widget.isin,
symbol: _selectedTicker ?? widget.selectedTicker,
onExchangeChanged: _handleExchangeChanged,
onForceRefresh: _handleForceRefresh,
),
theme.cardSurface,
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
FundamentalsTab(symbol: _selectedTicker ?? widget.symbol),
TechnicalTab(symbol: _selectedTicker ?? widget.symbol),
TradesTab(symbol: widget.symbol),
],
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
TabBar(
controller: _tabController,
labelColor: theme.primaryColor,
unselectedLabelColor: theme.textMuted,
indicatorColor: theme.primaryColor,
dividerColor: Colors.transparent,
labelStyle: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 13),
tabs: const [
Tab(text: 'OVERVIEW'),
Tab(text: 'TECHNICAL'),
Tab(text: 'TRADES'),
],
),
theme.cardSurface,
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
FundamentalsTab(
isin: widget.isin,
symbol: _selectedTicker,
),
TechnicalTab(
isin: widget.isin,
symbol: _selectedTicker,
),
TradesTab(symbol: widget.isin),
],
),
),
),
);
}
}
@@ -135,11 +159,13 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
@override
double get minExtent => _tabBar.preferredSize.height;
@override
double get maxExtent => _tabBar.preferredSize.height;
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: _backgroundColor,
child: _tabBar,