diff --git a/evently/android/app/build.gradle b/evently/android/app/build.gradle index 8f00827493..63ae673595 100644 --- a/evently/android/app/build.gradle +++ b/evently/android/app/build.gradle @@ -52,7 +52,7 @@ android { } signingConfigs { - debug { + debug { storeFile file('keystore.jks') storePassword 'tech.pylons' keyAlias 'key_tech_pylon' @@ -61,7 +61,7 @@ android { } buildTypes { - release { + release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug diff --git a/evently/lib/screens/detail_screen.dart b/evently/lib/screens/detail_screen.dart index ec4d57a16a..5d62fde20b 100644 --- a/evently/lib/screens/detail_screen.dart +++ b/evently/lib/screens/detail_screen.dart @@ -13,8 +13,10 @@ import 'package:evently/utils/space_utils.dart'; import 'package:evently/viewmodels/create_event_viewmodel.dart'; import 'package:evently/widgets/evently_text_field.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; +import 'package:intl/intl.dart'; class DetailsScreen extends StatefulWidget { const DetailsScreen({super.key}); @@ -24,6 +26,8 @@ class DetailsScreen extends StatefulWidget { } class _DetailsScreenState extends State { + final GlobalKey scaffoldMessengerKey = GlobalKey(); + @override void initState() { super.initState(); @@ -32,192 +36,241 @@ class _DetailsScreenState extends State { @override Widget build(BuildContext context) { final createEventViewModel = context.watch(); + final provider = context.watch(); - return Scaffold( - body: SingleChildScrollView( - child: Consumer( - builder: (_, provider, __) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const VerticalSpace(20), - MyStepsIndicator(currentStep: createEventViewModel.currentStep), - StepLabels(currentPage: createEventViewModel.currentPage, currentStep: createEventViewModel.currentStep), - const VerticalSpace(20), - PageAppBar(onPressBack: () { - createEventViewModel.previousPage(); - }), - Padding( - padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 15.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: GestureDetector( - onTap: () async { - _showDatePicker(onSelected: (DateTime? val) { - if (val == null) return; - provider.setStartDate = _dateFormatter(val); - }); - }, - child: EventlyTextField( + return ScaffoldMessenger( + key: scaffoldMessengerKey, + child: Scaffold( + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const VerticalSpace(20), + MyStepsIndicator(currentStep: createEventViewModel.currentStep), + StepLabels(currentPage: createEventViewModel.currentPage, currentStep: createEventViewModel.currentStep), + const VerticalSpace(20), + PageAppBar(onPressBack: () { + createEventViewModel.previousPage(); + }), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 15.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () async { + _showDatePicker(onSelected: (DateTime? val) { + if (val == null) return; + provider.setStartDate = _formatDateToIso(val); + // Validate dates only if both dates are set + if (provider.endDate.isNotEmpty) { + _validateDates(provider); + } + }); + }, + child: EventlyTextField( enable: false, label: LocaleKeys.start_date.tr(), - controller: TextEditingController(text: provider.startDate), + controller: TextEditingController(text: _formatDateDisplay(provider.startDate)), textCapitalization: TextCapitalization.sentences, imageBackground: PngUtils.textFieldBottomLeft, - inputTextColor: EventlyAppTheme.kTextDarkBlue), + inputTextColor: EventlyAppTheme.kTextDarkBlue, + ), + ), ), - ), - HorizontalSpace(20.w), - Expanded( - child: GestureDetector( - onTap: () { - _showDatePicker(onSelected: (DateTime? val) { - if (val == null) return; - provider.setEndDate = _dateFormatter(val); - }); - }, - child: EventlyTextField( + HorizontalSpace(20.w), + Expanded( + child: GestureDetector( + onTap: () { + _showDatePicker(onSelected: (DateTime? val) { + if (val == null) return; + provider.setEndDate = _formatDateToIso(val); + // Validate dates only if both dates are set + if (provider.startDate.isNotEmpty) { + _validateDates(provider); + } + }); + }, + child: EventlyTextField( enable: false, label: LocaleKeys.end_date.tr(), - controller: TextEditingController(text: provider.endDate), + controller: TextEditingController(text: _formatDateDisplay(provider.endDate)), textCapitalization: TextCapitalization.sentences, - validator: (value) { - return null; - }, imageBackground: PngUtils.textFieldTopRight, - inputTextColor: EventlyAppTheme.kTextDarkBlue), + inputTextColor: EventlyAppTheme.kTextDarkBlue, + ), + ), ), - ), - ], - ), - VerticalSpace(20.h), - Row( - children: [ - Expanded( - child: GestureDetector( - onTap: () { - _showTimerPicker(onSelected: (TimeOfDay? timeOfDay) { - if (timeOfDay == null) return; - final currentTime = DateTime.now(); - final time = currentTime.copyWith(hour: timeOfDay.hour, minute: timeOfDay.minute); - provider.setStartTime = _timerFormatter(time); - }); - }, - child: EventlyTextField( + ], + ), + VerticalSpace(20.h), + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + _showTimePicker(onSelected: (TimeOfDay? timeOfDay) { + if (timeOfDay == null) return; + final currentTime = DateTime.now(); + final time = currentTime.copyWith(hour: timeOfDay.hour, minute: timeOfDay.minute); + provider.setStartTime = _formatTimeToIso(time); + }); + }, + child: EventlyTextField( enable: false, label: LocaleKeys.start_time.tr(), controller: TextEditingController(text: provider.startTime), textCapitalization: TextCapitalization.sentences, - validator: (value) { - return null; - }, imageBackground: PngUtils.textFieldBottomLeft, - inputTextColor: EventlyAppTheme.kTextDarkBlue), + inputTextColor: EventlyAppTheme.kTextDarkBlue, + ), + ), ), - ), - HorizontalSpace(20.w), - Expanded( - child: GestureDetector( - onTap: () { - _showTimerPicker(onSelected: (TimeOfDay? timeOfDay) { - if (timeOfDay == null) return; - final currentTime = DateTime.now(); - final time = currentTime.copyWith(hour: timeOfDay.hour, minute: timeOfDay.minute); - provider.setEndTime = _timerFormatter(time); - }); - }, - child: EventlyTextField( - enable: false, - label: LocaleKeys.end_time.tr(), - controller: TextEditingController(text: provider.endTime), - textCapitalization: TextCapitalization.sentences, - validator: (value) { - return null; + HorizontalSpace(20.w), + Expanded( + child: GestureDetector( + onTap: () { + _showTimePicker(onSelected: (TimeOfDay? timeOfDay) { + if (timeOfDay == null) return; + final currentTime = DateTime.now(); + final time = currentTime.copyWith(hour: timeOfDay.hour, minute: timeOfDay.minute); + provider.setEndTime = _formatTimeToIso(time); + }); }, - imageBackground: PngUtils.textFieldTopRight, - inputTextColor: EventlyAppTheme.kTextDarkBlue, + child: EventlyTextField( + enable: false, + label: LocaleKeys.end_time.tr(), + controller: TextEditingController(text: provider.endTime), + textCapitalization: TextCapitalization.sentences, + imageBackground: PngUtils.textFieldTopRight, + inputTextColor: EventlyAppTheme.kTextDarkBlue, + ), ), ), - ), - ], - ), - VerticalSpace(20.h), - EventlyTextField( - onChanged: (_) => provider.setLocation = _, - label: LocaleKeys.location.tr(), - hint: LocaleKeys.search_location.tr(), - controller: TextEditingController(text: provider.location) - ..selection = TextSelection.fromPosition( - TextPosition(offset: provider.location.length), - ), - textCapitalization: TextCapitalization.sentences, - validator: (value) { - return null; - }, - ), - VerticalSpace(20.h), - EventlyTextField( - onChanged: (_) => provider.setDescription = _, - label: LocaleKeys.description.tr(), - hint: LocaleKeys.what_event_for.tr(), - controller: TextEditingController(text: provider.description) - ..selection = TextSelection.fromPosition( - TextPosition(offset: provider.description.length), - ), - textCapitalization: TextCapitalization.sentences, - noOfLines: 4, - ), - const VerticalSpace(40), - BottomButtons( - onPressContinue: () { - createEventViewModel.nextPage(); - }, - onPressSaveDraft: () { - final navigator = Navigator.of(context); - provider.saveAsDraft( - onCompleted: () => navigator.popUntil((route) => route.settings.name == RouteUtil.kRouteEventHub), - uploadStep: UploadStep.detail, - ); - }, - isContinueEnable: provider.startDate.isNotEmpty && - provider.endDate.isNotEmpty && - provider.startTime.isNotEmpty && - provider.endTime.isNotEmpty && - provider.description.isNotEmpty && - provider.location.isNotEmpty, - ), - ], + ], + ), + VerticalSpace(20.h), + EventlyTextField( + onChanged: (val) => provider.setLocation = val, + label: LocaleKeys.location.tr(), + hint: LocaleKeys.search_location.tr(), + controller: TextEditingController(text: provider.location) + ..selection = TextSelection.fromPosition( + TextPosition(offset: provider.location.length), + ), + textCapitalization: TextCapitalization.sentences, + ), + VerticalSpace(20.h), + EventlyTextField( + onChanged: (val) => provider.setDescription = val, + label: LocaleKeys.description.tr(), + hint: LocaleKeys.what_event_for.tr(), + controller: TextEditingController(text: provider.description) + ..selection = TextSelection.fromPosition( + TextPosition(offset: provider.description.length), + ), + textCapitalization: TextCapitalization.sentences, + noOfLines: 4, + ), + const VerticalSpace(40), + BottomButtons( + onPressContinue: () { + if (_validateDates(provider)) { + createEventViewModel.nextPage(); + } else { + _showSnackBarWithPostFrameCallback('End date cannot be before start date!'); + } + }, + onPressSaveDraft: () { + final navigator = Navigator.of(context); + if (_validateDates(provider)) { + provider.saveAsDraft( + onCompleted: () => navigator.popUntil((route) => route.settings.name == RouteUtil.kRouteEventHub), + uploadStep: UploadStep.detail, + ); + } else { + _showSnackBarWithPostFrameCallback('End date cannot be before start date!'); + } + }, + isContinueEnable: provider.startDate.isNotEmpty && + provider.endDate.isNotEmpty && + provider.startTime.isNotEmpty && + provider.endTime.isNotEmpty && + provider.description.isNotEmpty && + provider.location.isNotEmpty, + ), + ], + ), ), - ), - ], + ], + ), ), - )), + ), ); } - _showTimerPicker({required ValueChanged onSelected}) { - showTimePicker( - initialTime: TimeOfDay.now(), - context: context, - ).then((value) => onSelected(value!)); - } - _showDatePicker({required ValueChanged onSelected}) { showDatePicker( context: context, firstDate: DateTime.now(), lastDate: DateTime(2099, 12), - ).then((value) => onSelected(value!)); + ).then((value) => onSelected(value)); + } + + _showTimePicker({required ValueChanged onSelected}) { + showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ).then((value) => onSelected(value)); } - _timerFormatter(DateTime dateTime) { - return DateFormat('hh:mm a').format(dateTime); + String _formatDateToIso(DateTime? dateTime) { + if (dateTime == null) return ''; + return DateFormat('yyyy-MM-dd').format(dateTime); + } + + String _formatDateDisplay(String? date) { + if (date == null || date.isEmpty) return ''; + try { + final DateTime parsedDate = DateFormat('yyyy-MM-dd').parse(date); + return DateFormat.yMMMMd('en_US').format(parsedDate); + } catch (e) { + return ''; + } + } + + String _formatTimeToIso(DateTime? dateTime) { + if (dateTime == null) return ''; + return DateFormat('HH:mm').format(dateTime); + } + + bool _validateDates(EventlyProvider provider) { + if (provider.startDate.isEmpty || provider.endDate.isEmpty) { + return true; // Dates are not set yet, so no validation needed + } + + try { + final DateTime start = DateFormat('yyyy-MM-dd').parse(provider.startDate); + final DateTime end = DateFormat('yyyy-MM-dd').parse(provider.endDate); + if (end.isBefore(start)) { + return false; // End date is before start date + } + } catch (e) { + return false; // Invalid date format + } + + return true; // Dates are valid } - _dateFormatter(DateTime dateTime) { - return DateFormat.yMMMMd('en_US').format(dateTime); + void _showSnackBarWithPostFrameCallback(String message) { + SchedulerBinding.instance!.addPostFrameCallback((_) { + scaffoldMessengerKey.currentState!.showSnackBar(SnackBar( + content: Text(message), + duration: Duration(seconds: 2), + )); + }); } } diff --git a/evently/pubspec.lock b/evently/pubspec.lock index ce96ed57ce..af0ac58ecf 100644 --- a/evently/pubspec.lock +++ b/evently/pubspec.lock @@ -529,10 +529,10 @@ packages: dependency: "direct main" description: name: injectable - sha256: "3d98967224a5fdd4094a61bf53ed9616c3fbcf3e090bf83e7cb7d436d0c20041" + sha256: "3c8355a29d11ff28c0311bed754649761f345ef7a13ff66a714380954af51226" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" injectable_generator: dependency: "direct main" description: @@ -545,10 +545,10 @@ packages: dependency: transitive description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" io: dependency: transitive description: @@ -577,26 +577,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -641,10 +641,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" mime: dependency: transitive description: @@ -1101,10 +1101,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" timing: dependency: transitive description: @@ -1245,10 +1245,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.1" watcher: dependency: transitive description: