diff --git a/lib/core/application/event/create_event_bloc/create_event_bloc.dart b/lib/core/application/event/create_event_bloc/create_event_bloc.dart index 8e405ab93..418099d8d 100644 --- a/lib/core/application/event/create_event_bloc/create_event_bloc.dart +++ b/lib/core/application/event/create_event_bloc/create_event_bloc.dart @@ -157,8 +157,8 @@ class CreateEventBloc extends Bloc { description: state.description, private: state.private, approval_required: state.approvalRequired, - start: DateTime.parse(event.start.toIso8601String()), - end: DateTime.parse(event.end.toIso8601String()), + start: event.start.toUtc(), + end: event.end.toUtc(), timezone: event.timezone, guest_limit: state.guestLimit != null ? double.parse(state.guestLimit!) : null, diff --git a/lib/core/application/event/event_datetime_settings_bloc/event_datetime_settings_bloc.dart b/lib/core/application/event/event_datetime_settings_bloc/event_datetime_settings_bloc.dart index dae36440f..cbabab03e 100644 --- a/lib/core/application/event/event_datetime_settings_bloc/event_datetime_settings_bloc.dart +++ b/lib/core/application/event/event_datetime_settings_bloc/event_datetime_settings_bloc.dart @@ -10,7 +10,7 @@ import 'package:formz/formz.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:injectable/injectable.dart'; import 'package:app/core/utils/date_utils.dart' as date_utils; -import 'package:timezone/timezone.dart'; +import 'package:timezone/timezone.dart' as tz; part 'event_datetime_settings_bloc.freezed.dart'; @@ -32,10 +32,13 @@ class EventDateTimeSettingsBloc emit(state.copyWith(status: FormzSubmissionStatus.initial)); final finalTimezone = event.timezone ?? date_utils.DateUtils.getUserTimezoneOptionValue(); + final location = tz.getLocation(finalTimezone); + final start = tz.TZDateTime.from(event.startDateTime, location); + final end = tz.TZDateTime.from(event.endDateTime, location); emit( state.copyWith( - start: DateTimeFormz.dirty(event.startDateTime), - end: DateTimeFormz.dirty(event.endDateTime), + start: DateTimeFormz.dirty(start), + end: DateTimeFormz.dirty(end), timezone: finalTimezone, ), ); @@ -48,16 +51,7 @@ class EventDateTimeSettingsBloc emit(state.copyWith(status: FormzSubmissionStatus.inProgress)); final newStart = event.newStart; final newEnd = event.newEnd; - final location = getLocation(state.timezone ?? ''); - final startUtcDateTime = newStart - .add(Duration(milliseconds: location.currentTimeZone.offset * -1)); - final endUtcDateTime = newEnd - .add(Duration(milliseconds: location.currentTimeZone.offset * -1)); - - final now = DateTime.now(); - final tomorrowMidnight = DateTime.utc(now.year, now.month, now.day + 1); - if (newStart.isBefore(tomorrowMidnight) || - newEnd.isBefore(tomorrowMidnight)) { + if (newStart.isBefore(DateTime.now())) { emit( state.copyWith( start: DateTimeFormz.dirty(newStart), @@ -82,8 +76,8 @@ class EventDateTimeSettingsBloc if (event.event != null) { final result = await eventRepository.updateEvent( input: Input$EventInput( - start: DateTime.parse(startUtcDateTime.toIso8601String()), - end: DateTime.parse(endUtcDateTime.toIso8601String()), + start: newStart.toUtc(), + end: newEnd.toUtc(), ), id: event.event?.id ?? '', ); @@ -117,18 +111,30 @@ class EventDateTimeSettingsBloc Emitter emit, ) async { emit(state.copyWith(status: FormzSubmissionStatus.inProgress)); + final location = tz.getLocation(event.timezone); + final newStart = tz.TZDateTime( + location, + state.start.value!.year, + state.start.value!.month, + state.start.value!.day, + state.start.value!.hour, + state.start.value!.minute, + ); + + final newEnd = tz.TZDateTime( + location, + state.end.value!.year, + state.end.value!.month, + state.end.value!.day, + state.end.value!.hour, + state.end.value!.minute, + ); // Edit mode if (event.event != null) { - final location = getLocation(event.timezone); - final startUtcDateTime = state.start.value! - .add(Duration(milliseconds: location.currentTimeZone.offset * -1)); - final endUtcDateTime = state.end.value! - .add(Duration(milliseconds: location.currentTimeZone.offset * -1)); - final result = await eventRepository.updateEvent( input: Input$EventInput( - start: DateTime.parse(startUtcDateTime.toIso8601String()), - end: DateTime.parse(endUtcDateTime.toIso8601String()), + start: newStart.toUtc(), + end: newEnd.toUtc(), timezone: event.timezone, ), id: event.event?.id ?? '', @@ -147,6 +153,8 @@ class EventDateTimeSettingsBloc } else { emit( state.copyWith( + start: DateTimeFormz.dirty(newStart), + end: DateTimeFormz.dirty(newEnd), timezone: event.timezone, isValid: true, status: FormzSubmissionStatus.success, diff --git a/lib/core/constants/event/event_constants.dart b/lib/core/constants/event/event_constants.dart index 4e2791edd..bb56cef03 100644 --- a/lib/core/constants/event/event_constants.dart +++ b/lib/core/constants/event/event_constants.dart @@ -1,4 +1,5 @@ import 'package:timezone/timezone.dart' as tz; +import 'package:app/core/utils/date_utils.dart' as date_utils; class EventConstants { /// Guest limit per 2 @@ -287,19 +288,25 @@ class EventConstants { } class EventDateTimeConstants { - static final DateTime currentDateTime = DateTime.now(); + static DateTime get currentDateTime => tz.TZDateTime.now( + tz.getLocation(date_utils.DateUtils.getUserTimezoneOptionValue()), + ); - static final DateTime defaultStartDateTime = DateTime.utc( - currentDateTime.year, - currentDateTime.month, - currentDateTime.day + 3, - 10, - ); + static DateTime get defaultStartDateTime { + return DateTime( + currentDateTime.year, + currentDateTime.month, + currentDateTime.day + 3, + 10, + ); + } - static final DateTime defaultEndDateTime = DateTime.utc( - currentDateTime.year, - currentDateTime.month, - currentDateTime.day + 6, - 18, - ); + static DateTime get defaultEndDateTime { + return DateTime( + currentDateTime.year, + currentDateTime.month, + currentDateTime.day + 6, + 18, + ); + } } diff --git a/lib/core/presentation/pages/event/create_event/create_event_page.dart b/lib/core/presentation/pages/event/create_event/create_event_page.dart index 86ceff9d2..af435a0d5 100644 --- a/lib/core/presentation/pages/event/create_event/create_event_page.dart +++ b/lib/core/presentation/pages/event/create_event/create_event_page.dart @@ -6,7 +6,6 @@ import 'package:app/core/application/event/event_location_setting_bloc/event_loc import 'package:app/core/constants/event/event_constants.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; - import 'package:flutter_bloc/flutter_bloc.dart'; @RoutePage() diff --git a/lib/core/presentation/pages/event/event_control_panel_page/sub_pages/event_datetime_settings_page/event_datetime_settings_page.dart b/lib/core/presentation/pages/event/event_control_panel_page/sub_pages/event_datetime_settings_page/event_datetime_settings_page.dart index a4b1f5ac5..94a4b0bf1 100644 --- a/lib/core/presentation/pages/event/event_control_panel_page/sub_pages/event_datetime_settings_page/event_datetime_settings_page.dart +++ b/lib/core/presentation/pages/event/event_control_panel_page/sub_pages/event_datetime_settings_page/event_datetime_settings_page.dart @@ -13,8 +13,8 @@ import 'package:app/theme/spacing.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:app/core/utils/date_utils.dart' as date_utils; import 'package:formz/formz.dart'; +import 'package:timezone/timezone.dart' as tz; @RoutePage() class EventDatetimeSettingsPage extends StatelessWidget { @@ -66,8 +66,8 @@ class _EventDatetimeSettingsPageState bool? expandedStarts; bool? expandedEnds; - DateTime? tempStart; - DateTime? tempEnd; + late DateTime tempStart; + late DateTime tempEnd; @override void initState() { @@ -77,8 +77,8 @@ class _EventDatetimeSettingsPageState setState(() { expandedStarts = widget.expandedStarts; expandedEnds = widget.expandedEnds; - tempStart = start; - tempEnd = end; + tempStart = start!; + tempEnd = end!; }); } @@ -118,7 +118,7 @@ class _EventDatetimeSettingsPageState backgroundColor: LemonColor.atomicBlack, resizeToAvoidBottomInset: true, body: - BlocListener( + BlocConsumer( listener: (context, state) async { if (state.isValid == false && state.status == FormzSubmissionStatus.failure && @@ -134,140 +134,152 @@ class _EventDatetimeSettingsPageState showIconContainer: true, iconContainerColor: LemonColor.acidGreen, ); - AutoRouter.of(context).pop(); - await Future.delayed(const Duration(milliseconds: 500)); context.read().add( GetEventDetailEvent.fetch( eventId: widget.event!.id ?? '', ), ); + AutoRouter.of(context).pop(); } }, - child: Stack( - children: [ - CustomScrollView( - slivers: [ - SliverPadding( - padding: EdgeInsets.symmetric( - horizontal: Spacing.smMedium, - ), - sliver: SliverToBoxAdapter( - child: EventDatetimeSettingRowItem( - label: t.event.datetimeSettings.starts, - dotColor: LemonColor.snackBarSuccess, - selectedDateTime: tempStart ?? DateTime.now(), - expanded: expandedStarts == true, - onSelectTab: () { - setState(() { - expandedStarts = true; - expandedEnds = false; - }); - }, - onDateChanged: (DateTime datetime) { - setState(() { - tempStart = date_utils.DateUtils.combineDateAndTime( - datetime, - TimeOfDay( - hour: tempStart!.hour, - minute: tempStart!.minute, - ), - ); - }); - }, - onTimeChanged: (TimeOfDay timeOfDay) { - setState(() { - tempStart = date_utils.DateUtils.combineDateAndTime( - tempStart ?? DateTime.now(), - timeOfDay, - ); - }); - }, + builder: (context, state) { + final location = tz.getLocation(state.timezone ?? ''); + return Stack( + children: [ + CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.symmetric( + horizontal: Spacing.smMedium, ), - ), - ), - SliverPadding( - padding: EdgeInsets.only( - left: Spacing.smMedium, - right: Spacing.smMedium, - bottom: Spacing.smMedium, - ), - sliver: SliverToBoxAdapter( - child: Container( - height: 1, - color: LemonColor.white03, + sliver: SliverToBoxAdapter( + child: EventDatetimeSettingRowItem( + label: t.event.datetimeSettings.starts, + dotColor: LemonColor.snackBarSuccess, + selectedDateTime: tempStart, + expanded: expandedStarts == true, + onSelectTab: () { + setState(() { + expandedStarts = true; + expandedEnds = false; + }); + }, + onDateChanged: (DateTime datetime) { + setState(() { + tempStart = tz.TZDateTime( + location, + datetime.year, + datetime.month, + datetime.day, + tempStart.hour, + tempStart.minute, + ); + }); + }, + onTimeChanged: (TimeOfDay timeOfDay) { + setState(() { + tempStart = tz.TZDateTime( + location, + tempStart.year, + tempStart.month, + tempStart.day, + timeOfDay.hour, + timeOfDay.minute, + ); + }); + }, + ), ), ), - ), - SliverPadding( - padding: EdgeInsets.symmetric( - horizontal: Spacing.smMedium, - ), - sliver: SliverToBoxAdapter( - child: EventDatetimeSettingRowItem( - label: t.event.datetimeSettings.ends, - dotColor: LemonColor.coralReef, - expanded: expandedEnds == true, - selectedDateTime: tempEnd ?? DateTime.now(), - onSelectTab: () { - setState(() { - expandedStarts = false; - expandedEnds = true; - }); - }, - onDateChanged: (DateTime datetime) { - setState(() { - tempEnd = date_utils.DateUtils.combineDateAndTime( - datetime, - TimeOfDay( - hour: tempEnd!.hour, - minute: tempEnd!.minute, - ), - ); - }); - }, - onTimeChanged: (TimeOfDay timeOfDay) { - setState(() { - tempEnd = date_utils.DateUtils.combineDateAndTime( - tempEnd ?? DateTime.now(), - timeOfDay, - ); - }); - }, + SliverPadding( + padding: EdgeInsets.only( + left: Spacing.smMedium, + right: Spacing.smMedium, + bottom: Spacing.smMedium, + ), + sliver: SliverToBoxAdapter( + child: Container( + height: 1, + color: LemonColor.white03, + ), ), ), - ), - ], - ), - BlocBuilder( - builder: (context, state) { - return Align( - alignment: Alignment.bottomCenter, - child: Container( - padding: EdgeInsets.all(Spacing.smMedium), - child: SafeArea( - child: LinearGradientButton.primaryButton( - onTap: () { - context.read().add( - EventDateTimeSettingsEventSaveChangesDateTime( - event: widget.event, - newStart: tempStart ?? DateTime.now(), - newEnd: tempEnd ?? DateTime.now(), - ), - ); + SliverPadding( + padding: EdgeInsets.symmetric( + horizontal: Spacing.smMedium, + ), + sliver: SliverToBoxAdapter( + child: EventDatetimeSettingRowItem( + label: t.event.datetimeSettings.ends, + dotColor: LemonColor.coralReef, + expanded: expandedEnds == true, + selectedDateTime: tempEnd, + onSelectTab: () { + setState(() { + expandedStarts = false; + expandedEnds = true; + }); + }, + onDateChanged: (DateTime datetime) { + setState(() { + tempEnd = tz.TZDateTime( + location, + datetime.year, + datetime.month, + datetime.day, + tempEnd.hour, + tempEnd.minute, + ); + }); + }, + onTimeChanged: (TimeOfDay timeOfDay) { + setState(() { + tempEnd = tz.TZDateTime( + location, + tempEnd.year, + tempEnd.month, + tempEnd.day, + timeOfDay.hour, + timeOfDay.minute, + ); + }); }, - label: t.common.actions.saveChanges, - textColor: colorScheme.onPrimary, - loadingWhen: - state.status == FormzSubmissionStatus.inProgress, ), ), ), - ); - }, - ), - ], - ), + ], + ), + BlocBuilder( + builder: (context, state) { + return Align( + alignment: Alignment.bottomCenter, + child: Container( + padding: EdgeInsets.all(Spacing.smMedium), + child: SafeArea( + child: LinearGradientButton.primaryButton( + onTap: () { + context.read().add( + EventDateTimeSettingsEventSaveChangesDateTime( + event: widget.event, + newStart: tempStart, + newEnd: tempEnd, + ), + ); + }, + label: t.common.actions.saveChanges, + textColor: colorScheme.onPrimary, + loadingWhen: state.status == + FormzSubmissionStatus.inProgress, + ), + ), + ), + ); + }, + ), + ], + ); + }, ), ), ); diff --git a/lib/core/presentation/pages/event/event_detail_page/guest_event_detail_page/widgets/guest_event_detail_general_info.dart b/lib/core/presentation/pages/event/event_detail_page/guest_event_detail_page/widgets/guest_event_detail_general_info.dart index 4e4fc59a0..b002ec873 100644 --- a/lib/core/presentation/pages/event/event_detail_page/guest_event_detail_page/widgets/guest_event_detail_general_info.dart +++ b/lib/core/presentation/pages/event/event_detail_page/guest_event_detail_page/widgets/guest_event_detail_general_info.dart @@ -108,7 +108,7 @@ class _InfoCard extends StatelessWidget { final formattedEndDate = DateFormatUtils.dateWithTimezone( dateTime: event.end ?? DateTime.now(), timezone: event.timezone ?? '', - pattern: 'MMM, d, hh:mm a', + pattern: 'MMM d, hh:mm a', ); final wigets = [ Padding( diff --git a/lib/core/utils/date_format_utils.dart b/lib/core/utils/date_format_utils.dart index 3fc3d86b1..15963a80b 100644 --- a/lib/core/utils/date_format_utils.dart +++ b/lib/core/utils/date_format_utils.dart @@ -1,5 +1,6 @@ import 'package:app/core/utils/date_utils.dart'; import 'package:intl/intl.dart'; +import 'package:timezone/timezone.dart' as tz; class DateFormatUtils { static const String fullDateFormat = 'EE, MMM d • hh:mm a'; @@ -41,7 +42,11 @@ class DateFormatUtils { String? pattern = 'MMM d, yyyy h:mm a', bool withTimezoneOffset = true, }) { - final formattedDate = DateFormat(pattern).format(dateTime.toLocal()); + final location = + timezone.isNotEmpty == true ? tz.getLocation(timezone) : null; + final timezoneDateTime = + location != null ? tz.TZDateTime.from(dateTime, location) : dateTime; + final formattedDate = DateFormat(pattern).format(timezoneDateTime); return withTimezoneOffset ? '$formattedDate ${DateUtils.getGMTOffsetText(timezone)}' : formattedDate; diff --git a/lib/core/utils/event_utils.dart b/lib/core/utils/event_utils.dart index ab97846c5..369609c24 100644 --- a/lib/core/utils/event_utils.dart +++ b/lib/core/utils/event_utils.dart @@ -8,6 +8,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:app/core/utils/date_utils.dart' as date_utils; import 'package:duration/duration.dart'; import 'package:intl/intl.dart'; +import 'package:timezone/timezone.dart' as tz; class EventUtils { static bool isAttending({required Event event, required String userId}) { @@ -160,21 +161,25 @@ class EventUtils { /// Returns a tuple of (formattedDate, formattedTime). /// If event data is incomplete, returns empty strings. static (String, String) getFormattedEventDateAndTime(Event event) { - if (event.start == null || event.end == null || event.timezone == null) { + if (event.start == null || + event.end == null || + event.timezone?.isNotEmpty != true) { return ('', ''); } - + final location = tz.getLocation(event.timezone!); final isSameDay = event.start!.year == event.end!.year && event.start!.month == event.end!.month && event.start!.day == event.end!.day; + final timezoneStartDate = tz.TZDateTime.from(event.start!, location); + final timezoneEndDate = tz.TZDateTime.from(event.end!, location); final dateFormatter = DateFormat('EEEE, MMMM d'); final timeFormatter = DateFormat('h:mm a'); final endDateFormatter = DateFormat('MMMM d'); - final startDateStr = dateFormatter.format(event.start!); - final startTimeStr = timeFormatter.format(event.start!); - final endTimeStr = timeFormatter.format(event.end!); + final startDateStr = dateFormatter.format(timezoneStartDate); + final startTimeStr = timeFormatter.format(timezoneStartDate); + final endTimeStr = timeFormatter.format(timezoneEndDate); final gmtOffset = DateUtils.getGMTOffsetText(event.timezone!); // Format output based on whether it's a single-day or multi-day event