From ad41a2a87eb383a335fc236190e5b930e34b52ed Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 6 May 2025 14:59:54 +0300 Subject: [PATCH] Implemented calendar widget and bloc. --- .../analytics_date_picker_bloc.dart | 17 ++ .../analytics_date_picker_event.dart | 17 ++ .../analytics_tab_bloc.dart | 0 .../analytics_tab_event.dart | 0 .../analytics/views/analytics_page.dart | 2 +- .../widgets/analytics_date_filter_button.dart | 111 ++++++---- .../widgets/analytics_page_tab_button.dart | 2 +- .../analytics_page_tabs_and_children.dart | 2 +- .../widgets/month_picker_widget.dart | 194 ++++++++++++++++++ lib/utils/color_manager.dart | 1 + 10 files changed, 305 insertions(+), 41 deletions(-) create mode 100644 lib/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart create mode 100644 lib/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_event.dart rename lib/pages/analytics/modules/analytics/blocs/{bloc => analytics_tab}/analytics_tab_bloc.dart (100%) rename lib/pages/analytics/modules/analytics/blocs/{bloc => analytics_tab}/analytics_tab_event.dart (100%) create mode 100644 lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart diff --git a/lib/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart b/lib/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart new file mode 100644 index 00000000..b848f79f --- /dev/null +++ b/lib/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart @@ -0,0 +1,17 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'analytics_date_picker_event.dart'; + +class AnalyticsDatePickerBloc extends Bloc { + AnalyticsDatePickerBloc() : super(DateTime.now()) { + on(_onUpdateAnalyticsDatePickerEvent); + } + + void _onUpdateAnalyticsDatePickerEvent( + UpdateAnalyticsDatePickerEvent event, + Emitter emit, + ) { + emit(event.date); + } +} diff --git a/lib/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_event.dart b/lib/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_event.dart new file mode 100644 index 00000000..4fdb265e --- /dev/null +++ b/lib/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_event.dart @@ -0,0 +1,17 @@ +part of 'analytics_date_picker_bloc.dart'; + +sealed class AnalyticsDatePickerEvent extends Equatable { + const AnalyticsDatePickerEvent(); + + @override + List get props => []; +} + +final class UpdateAnalyticsDatePickerEvent extends AnalyticsDatePickerEvent { + const UpdateAnalyticsDatePickerEvent(this.date); + + final DateTime date; + + @override + List get props => [date]; +} diff --git a/lib/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart b/lib/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart similarity index 100% rename from lib/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart rename to lib/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart diff --git a/lib/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_event.dart b/lib/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_event.dart similarity index 100% rename from lib/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_event.dart rename to lib/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_event.dart diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index fcd84a45..43cc4330 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart'; diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart index 1b3ba5e4..0cef6257 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart @@ -1,57 +1,92 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:intl/intl.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/month_picker_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -class AnalyticsDateFilterButton extends StatelessWidget { +class AnalyticsDateFilterButton extends StatefulWidget { const AnalyticsDateFilterButton({super.key}); static final _color = ColorsManager.blackColor.withValues(alpha: 0.8); + @override + State createState() => + _AnalyticsDateFilterButtonState(); +} + +class _AnalyticsDateFilterButtonState extends State { + late final AnalyticsDatePickerBloc _analyticsDatePickerBloc; + @override + void initState() { + _analyticsDatePickerBloc = AnalyticsDatePickerBloc(); + super.initState(); + } + + @override + void dispose() { + _analyticsDatePickerBloc.close(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return TextButton.icon( - style: TextButton.styleFrom( - foregroundColor: _color, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.greyColor, - width: 1, + return BlocProvider.value( + value: _analyticsDatePickerBloc, + child: Builder(builder: (context) { + final selectedDate = context.watch().state; + return TextButton.icon( + style: TextButton.styleFrom( + foregroundColor: AnalyticsDateFilterButton._color, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.greyColor, + width: 1, + ), + ), + backgroundColor: ColorsManager.transparentColor, + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), ), - ), - backgroundColor: ColorsManager.transparentColor, - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 16, - ), - ), - icon: SvgPicture.asset( - Assets.blankCalendar, - height: 20, - width: 20, - colorFilter: ColorFilter.mode(_color, BlendMode.srcIn), - ), - label: Text( - _concatenateDate(DateTime(2024, 1), DateTime(2024, 12)), - style: const TextStyle( - fontWeight: FontWeight.w700, - ), - ), - onPressed: () {}, + icon: SvgPicture.asset( + Assets.blankCalendar, + height: 20, + width: 20, + colorFilter: + ColorFilter.mode(AnalyticsDateFilterButton._color, BlendMode.srcIn), + ), + label: Text( + _formatDate(selectedDate), + style: const TextStyle( + fontWeight: FontWeight.w700, + ), + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => MonthPickerWidget( + selectedDate: selectedDate, + onDateSelected: (value) { + _analyticsDatePickerBloc.add( + UpdateAnalyticsDatePickerEvent(value), + ); + }, + ), + ); + }, + ); + }), ); } - String _formatDate(DateTime date) { - final formatter = DateFormat('MMM yyyy'); - final formattedDate = formatter.format(date); + String _formatDate(DateTime? date) { + final formatter = DateFormat('MMMM yyyy'); + final formattedDate = formatter.format(date ?? DateTime.now()); return formattedDate; } - - String _concatenateDate(DateTime startDate, DateTime endDate) { - final formattedStartDate = _formatDate(startDate); - final formattedEndDate = _formatDate(endDate); - return '$formattedStartDate - $formattedEndDate'; - } } diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart index c12ed20d..fb0983fa 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart'; import 'package:syncrow_web/utils/color_manager.dart'; diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart index 1621634b..dae9b075 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/bloc/analytics_tab_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart'; diff --git a/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart b/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart new file mode 100644 index 00000000..53ba31af --- /dev/null +++ b/lib/pages/analytics/modules/analytics/widgets/month_picker_widget.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class MonthPickerWidget extends StatefulWidget { + const MonthPickerWidget({ + super.key, + required this.selectedDate, + required this.onDateSelected, + }); + + final DateTime selectedDate; + final ValueChanged? onDateSelected; + + @override + State createState() => _MonthPickerWidgetState(); +} + +class _MonthPickerWidgetState extends State { + late int _currentYear; + int? _selectedMonth; + + static const _monthNames = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ]; + + @override + void initState() { + super.initState(); + _currentYear = widget.selectedDate.year; + _selectedMonth = widget.selectedDate.month - 1; + } + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Theme.of(context).colorScheme.surface, + child: Container( + padding: const EdgeInsetsDirectional.all(20), + width: 320, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildYearSelector(), + _buildMonthsGrid(), + const SizedBox(height: 20), + Row( + spacing: 12, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FilledButton( + onPressed: () => Navigator.pop(context), + style: FilledButton.styleFrom( + fixedSize: const Size(106, 40), + backgroundColor: const Color(0xFFEDF2F7), + padding: const EdgeInsetsDirectional.symmetric( + vertical: 10, + horizontal: 16, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Text( + 'Cancel', + style: context.textTheme.titleSmall?.copyWith( + fontSize: 14, + fontWeight: FontWeight.w600, + color: ColorsManager.grey700, + ), + ), + ), + FilledButton( + onPressed: () { + Navigator.pop(context); + final date = DateTime( + _currentYear, + _selectedMonth! + 1, + ); + widget.onDateSelected?.call(date); + }, + style: FilledButton.styleFrom( + fixedSize: const Size(106, 40), + backgroundColor: ColorsManager.vividBlue.withValues( + alpha: 0.7, + ), + padding: const EdgeInsetsDirectional.symmetric( + vertical: 10, + horizontal: 16, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Text( + 'Done', + style: context.textTheme.titleSmall?.copyWith( + fontSize: 14, + fontWeight: FontWeight.w600, + color: ColorsManager.whiteColors, + ), + ), + ), + ], + ), + ], + ), + ), + ); + } + + Row _buildYearSelector() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '$_currentYear', + style: context.textTheme.titleSmall?.copyWith( + fontSize: 14, + fontWeight: FontWeight.w500, + color: ColorsManager.grey700, + ), + ), + const Spacer(), + IconButton( + onPressed: () => setState(() => _currentYear = _currentYear - 1), + icon: const Icon( + Icons.chevron_left, + color: ColorsManager.grey700, + ), + ), + IconButton( + onPressed: () => setState(() => _currentYear = _currentYear + 1), + icon: const Icon( + Icons.chevron_right, + color: ColorsManager.grey700, + ), + ), + ], + ); + } + + Widget _buildMonthsGrid() { + return GridView.builder( + shrinkWrap: true, + itemCount: 12, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(8), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 2.5, + mainAxisSpacing: 8, + mainAxisExtent: 30, + ), + itemBuilder: (context, index) { + final isSelected = _selectedMonth == index; + return InkWell( + onTap: () => setState(() => _selectedMonth = index), + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: isSelected + ? ColorsManager.vividBlue.withValues(alpha: 0.7) + : const Color(0xFFEDF2F7), + borderRadius: + isSelected ? BorderRadius.circular(15) : BorderRadius.zero, + ), + child: Text( + _monthNames[index], + style: context.textTheme.titleSmall?.copyWith( + fontSize: 12, + color: isSelected + ? ColorsManager.whiteColors + : ColorsManager.blackColor.withValues(alpha: 0.8), + fontWeight: FontWeight.w500, + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index a4bcc0da..5a892aa6 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -72,4 +72,5 @@ abstract class ColorsManager { //background: #F8F8F8; static const Color vividBlue = Color(0xFF023DFE); static const Color semiTransparentRed = Color(0x99FF0000); + static const Color grey700 = Color(0xFF2D3748); }