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,120 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../core/network/api_client.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/widgets/shimmer_loading.dart';
import '../../asset_detail/views/asset_detail_screen.dart';
import '../../discovery/cubit/discovery_cubit.dart';
import '../../favorites/cubit/favorites_cubit.dart';
/// Dynamic Scrollable Discovery Bar consuming DiscoveryCubit with real-time backend recommendations.
class AssetDiscoveryBar extends StatefulWidget {
final ApiClient apiClient;
const AssetDiscoveryBar({
super.key,
required this.apiClient,
});
@override
State<AssetDiscoveryBar> createState() => _AssetDiscoveryBarState();
}
class _AssetDiscoveryBarState extends State<AssetDiscoveryBar> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
final cubit = context.read<DiscoveryCubit>();
if (cubit.state.assets.isEmpty) {
cubit.loadDiscovery();
}
}
});
}
@override
Widget build(BuildContext context) {
final activeTheme = AppTheme.activePreset;
return BlocBuilder<DiscoveryCubit, DiscoveryState>(
builder: (context, discState) {
if (discState.isLoading && discState.assets.isEmpty) {
return SizedBox(
height: 38,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 6,
itemBuilder: (_, __) => const Padding(
padding: EdgeInsets.only(right: 8),
child: ShimmerLoading(width: 130, height: 38, borderRadius: 20),
),
),
);
}
final assets = discState.assets;
if (assets.isEmpty) {
return const SizedBox.shrink();
}
return BlocBuilder<FavoritesCubit, FavoritesState>(
builder: (context, favState) {
return SizedBox(
height: 38,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: assets.length,
itemBuilder: (context, index) {
final asset = assets[index];
final identifier = asset.symbol.isNotEmpty ? asset.symbol : asset.isin;
final isFav = favState.isFavorite(identifier) || favState.isFavorite(asset.isin);
return Padding(
padding: const EdgeInsets.only(right: 8),
child: ActionChip(
avatar: Icon(
isFav ? Icons.star_rounded : Icons.explore_outlined,
size: 16,
color: isFav ? Colors.amber : activeTheme.primaryColor,
),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
asset.name.isNotEmpty ? asset.name : asset.symbol,
style: TextStyle(
fontSize: 12,
color: activeTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
],
),
backgroundColor: activeTheme.glassSurface,
side: BorderSide(
color: isFav ? Colors.amber.withValues(alpha: 0.5) : activeTheme.glassBorder,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AssetDetailScreen(
symbol: identifier,
apiClient: widget.apiClient,
),
),
);
},
),
);
},
),
);
},
);
},
);
}
}