feat(App): update Finlytic Flutter app UI and blocs
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
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 '../bloc/calendar_bloc.dart';
|
||||
import '../repositories/calendar_repository.dart';
|
||||
import '../widgets/calendar_event_tile.dart';
|
||||
import '../widgets/month_calendar_widget.dart';
|
||||
|
||||
/// Corporate Calendar Screen featuring a compact interactive monthly calendar grid,
|
||||
/// event count indicators, category filters, and tile view (Kachelansicht) for events.
|
||||
class CorporateCalendarScreen extends StatelessWidget {
|
||||
final ApiClient apiClient;
|
||||
|
||||
const CorporateCalendarScreen({super.key, required this.apiClient});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => CalendarBloc(
|
||||
repository: CalendarRepository(apiClient: apiClient),
|
||||
)..add(FetchCalendarEvents()),
|
||||
child: _CorporateCalendarScreenContent(apiClient: apiClient),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CorporateCalendarScreenContent extends StatelessWidget {
|
||||
final ApiClient apiClient;
|
||||
|
||||
const _CorporateCalendarScreenContent({required this.apiClient});
|
||||
|
||||
static const List<String> categories = ['Alle', 'Earnings', 'ExDividend', 'Payout'];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocBuilder<CalendarBloc, CalendarState>(
|
||||
builder: (context, state) {
|
||||
if (state is CalendarLoading) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(color: AppTheme.primaryEmerald),
|
||||
);
|
||||
}
|
||||
if (state is CalendarError) {
|
||||
return Center(
|
||||
child: Text(state.message, style: TextStyle(color: AppTheme.textMuted)),
|
||||
);
|
||||
}
|
||||
if (state is CalendarLoaded) {
|
||||
final filtered = state.filteredEvents;
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MonthCalendarWidget(
|
||||
currentMonth: state.currentMonth,
|
||||
selectedDate: state.selectedDate,
|
||||
events: state.allEvents.map((e) => e.toJson()).toList(),
|
||||
onDateSelected: (date) {
|
||||
context.read<CalendarBloc>().add(FilterDateSelected(date));
|
||||
},
|
||||
onMonthChanged: (newMonth) {
|
||||
context.read<CalendarBloc>().add(MonthChanged(newMonth));
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: categories.map((cat) {
|
||||
final isSelected = state.selectedCategory == cat;
|
||||
String label = 'Alle';
|
||||
if (cat == 'Earnings') label = 'Quartalsergebnisse';
|
||||
if (cat == 'ExDividend') label = 'Ex-Dividendentage';
|
||||
if (cat == 'Payout') label = 'Zahlungstage';
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ChoiceChip(
|
||||
label: Text(label),
|
||||
selected: isSelected,
|
||||
selectedColor: AppTheme.primaryEmerald.withValues(alpha: 0.25),
|
||||
backgroundColor: AppTheme.glassSurface,
|
||||
labelStyle: TextStyle(
|
||||
color: isSelected ? AppTheme.primaryEmerald : AppTheme.textSecondary,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
fontSize: 12,
|
||||
),
|
||||
side: BorderSide(color: isSelected ? AppTheme.primaryEmerald : AppTheme.glassBorder),
|
||||
onSelected: (_) {
|
||||
context.read<CalendarBloc>().add(FilterCategoryChanged(cat));
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (state.selectedDate != null)
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
context.read<CalendarBloc>().add(const FilterDateSelected(null));
|
||||
},
|
||||
icon: Icon(Icons.clear, size: 14, color: AppTheme.accentCyan),
|
||||
label: Text('Alle Tage', style: TextStyle(color: AppTheme.accentCyan, fontSize: 12)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
state.selectedDate != null
|
||||
? 'Termine am ${state.selectedDate!.day.toString().padLeft(2, '0')}.${state.selectedDate!.month.toString().padLeft(2, '0')}.${state.selectedDate!.year} (${filtered.length})'
|
||||
: 'Anstehende Termine (${filtered.length})',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: AppTheme.textPrimary),
|
||||
),
|
||||
Text('Kachelansicht', style: TextStyle(fontSize: 11, color: AppTheme.textMuted)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
filtered.isEmpty
|
||||
? Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.glassSurface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppTheme.glassBorder),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Keine Unternehmenstermine für diesen Filter/Tag gefunden.',
|
||||
style: TextStyle(color: AppTheme.textMuted, fontSize: 13),
|
||||
),
|
||||
),
|
||||
)
|
||||
: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final crossAxisCount = constraints.maxWidth > 750 ? 4 : (constraints.maxWidth > 480 ? 2 : 1);
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: filtered.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
childAspectRatio: 3,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
return CalendarEventTile(
|
||||
event: filtered[index].toJson(),
|
||||
apiClient: apiClient,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user