Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cat-voices): Date & Time input widget #1224

Open
wants to merge 14 commits into
base: mve3
Choose a base branch
from

Conversation

LynxLynxx
Copy link
Contributor

@LynxLynxx LynxLynxx commented Nov 18, 2024

Description

This is a new component that uses two standard text-fields bonded together to provide a date/time selection widgets to setup the most important campaign dates.

Related Issue(s)

Resolves #1181

Description of Changes

Adding new widget with controllers to select date and time.

Nagranie.z.ekranu.2024-11-18.o.14.32.23.mov

Requirements

Manual input requirements / date

  • Text field behaviour is the same as text-fields.
  • manual input validation follows dd/mm/yyyy
  • During input show: Format: "dd/mm/yyyy" in red
  • Dates cannot be saved when validation does not resolve.

Manual input requirements / time

  • Text field behaviour is the same as text-fields.
  • manual input validation follows 00:00 (1-00, 24h / 1-00m, 60min)
  • During input show: Format: "00:00" in red
  • Time cannot be saved when validation does not resolve.

Widget guided select date

  • Pressing date icon, opens date select widget
  • Data range is today + 1yr
  • If user cancels out of widget, current date is set.
  • clear, set date to current date
  • month <> jumps one month back/forth
  • year dropdown shows years, current date + 1yr
  • On 'OK' date is filled in the text-field

Widget guided select time

  • Pressing time icon, opens time select widget
  • Show time with increments of 30min,
  • Show 9 timeslots + scroll
  • On select time is filled in the text-field

Screenshots

If applicable, add screenshots to help explain your changes.

Please confirm the following checks

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream module

@LynxLynxx LynxLynxx added do not merge yet PR is not ready to be merged yet comment me Request for comments ux/ui UX/UI related issues labels Nov 18, 2024
@LynxLynxx LynxLynxx self-assigned this Nov 18, 2024
@LynxLynxx LynxLynxx added review me PR is ready for review and removed do not merge yet PR is not ready to be merged yet comment me Request for comments labels Nov 18, 2024
@LynxLynxx LynxLynxx changed the base branch from main to mve3 November 18, 2024 13:42
Copy link
Contributor

Test Report | ${\color{lightgreen}Pass: 29/29}$ | ${\color{red}Fail: 0/29}$ |

@damian-molinski damian-molinski added the dart Pull requests that update Dart code label Nov 18, 2024
class VoicesDatePicker extends StatefulWidget {
final DatePickerController controller;
final String timeZone;
const VoicesDatePicker({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea for moving / renaming classes.

Let's say we have VoicesDateTimeTextField which have ValueChanged<DateTime> onChanged and simple controller(VoicesDateTimeController) which maybe extends TextEditingController or simply ValueNotifier<DateTime>.

Goal here is for VoicesDateTimeTextField to output only DateTime and accept it as input for VoicesDateTimeController.

This widget builds two childs in a row (will be more complex with decoration), VoicesDateTextField and VoicesTimeOfDayTextField both of which are stateless and accept TextEditingController from VoicesDateTimeTextField and have onCalendarTap / onClockTap respectively.

Now VoicesDateTimeTextField can launch correct picker and global keys can be private.
Ofc. VoicesDateTimeTextField is watching changes in both fields and merging them into final DateTime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good idea, but in this case, we have two possibilities about from where data come from: either textfield or picker. We also need to validate this data. If we want to separate controllers then your approach is better and I'm happy to do it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 controllers even. I think it will be easier to read and maintain

VoicesDateTimeTextField will have to open OverlayEntry and update respective controller, yes, and VoicesDateTextField / VoicesTimeOfDayTextField will have to watch changes in its controller and validate them but it's happening automatically i believe.

Pseudo code

// voices_date_time_text_field.dart

class VoicesDateTimeTextFieldController extends ValueNotifier<DateTime?> {
  VoicesDateTimeTextFieldController([super._value]);
}

class VoicesDateTimeTextField extends StatefulWidget {
  final VoicesDateTimeTextFieldController? controller;
  final ValueChanged<DateTime?>? onChanged;

  const VoicesDateTimeTextField({
    super.key,
    this.controller,
    this.onChanged,
  });

  @override
  State<VoicesDateTimeTextField> createState() {
    return _VoicesDateTimeTextFieldState();
  }
}

class _VoicesDateTimeTextFieldState extends State<VoicesDateTimeTextField> {
  late final VoicesDateTextFieldController _dateController;
  late final VoicesTimeOfDayTextFieldController _timeOfDayController;

  @override
  void initState() {
    super.initState();

    _dateController = VoicesDateTextFieldController()..addListener(_xxx);
    _timeOfDayController = VoicesTimeOfDayTextFieldController()
      ..addListener(_xxx);
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [],
    );
  }

  void _xxx() {
    //
  }
}

// voices_date_text_field.dart

class VoicesDateTextFieldController extends ValueNotifier<DateTime?> {
  VoicesDateTextFieldController([super._value]);
}

class VoicesDateTextField extends StatefulWidget {
  final VoicesDateTextFieldController controller;

  const VoicesDateTextField({
    super.key,
    required this.controller,
  });

  @override
  State<VoicesDateTextField> createState() => _VoicesDateTextFieldState();
}

class _VoicesDateTextFieldState extends State<VoicesDateTextField> {
  late final TextEditingController _textEditingController;

  @override
  void initState() {
    super.initState();
    _textEditingController = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    return VoicesTextField(
      controller: _textEditingController,
      onFieldSubmitted: (value) {},
    );
  }
  
  // listen _textEditingController, validate, parse and pass result to widget.controller
}

// voices_time_of_day_text_field.dart

class VoicesTimeOfDayTextFieldController extends ValueNotifier<TimeOfDay?> {
  VoicesTimeOfDayTextFieldController([super._value]);
}

class VoicesTimeOfDayTextField extends StatefulWidget {
  const VoicesTimeOfDayTextField({super.key});

  @override
  State<VoicesTimeOfDayTextField> createState() =>
      _VoicesTimeOfDayTextFieldState();
}

class _VoicesTimeOfDayTextFieldState extends State<VoicesTimeOfDayTextField> {
  @override
  Widget build(BuildContext context) {
    return VoicesTextField(
      onFieldSubmitted: (value) {},
    );
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure this will be harded to implement then what i just shown so we may discuss this with frontend team before committing to it

@damian-molinski
Copy link
Contributor

One more thing, make sure to you can jump focus from one text field to next with tab, probably exclude calendar/clock from focus or implement focus travel

…stency and state management across the application
),
),
),
onFieldSubmitted: (String value) {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nothing should happen here?

class VoicesDatePicker extends StatefulWidget {
final DatePickerController controller;
final String timeZone;
const VoicesDatePicker({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 controllers even. I think it will be easier to read and maintain

VoicesDateTimeTextField will have to open OverlayEntry and update respective controller, yes, and VoicesDateTextField / VoicesTimeOfDayTextField will have to watch changes in its controller and validate them but it's happening automatically i believe.

Pseudo code

// voices_date_time_text_field.dart

class VoicesDateTimeTextFieldController extends ValueNotifier<DateTime?> {
  VoicesDateTimeTextFieldController([super._value]);
}

class VoicesDateTimeTextField extends StatefulWidget {
  final VoicesDateTimeTextFieldController? controller;
  final ValueChanged<DateTime?>? onChanged;

  const VoicesDateTimeTextField({
    super.key,
    this.controller,
    this.onChanged,
  });

  @override
  State<VoicesDateTimeTextField> createState() {
    return _VoicesDateTimeTextFieldState();
  }
}

class _VoicesDateTimeTextFieldState extends State<VoicesDateTimeTextField> {
  late final VoicesDateTextFieldController _dateController;
  late final VoicesTimeOfDayTextFieldController _timeOfDayController;

  @override
  void initState() {
    super.initState();

    _dateController = VoicesDateTextFieldController()..addListener(_xxx);
    _timeOfDayController = VoicesTimeOfDayTextFieldController()
      ..addListener(_xxx);
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [],
    );
  }

  void _xxx() {
    //
  }
}

// voices_date_text_field.dart

class VoicesDateTextFieldController extends ValueNotifier<DateTime?> {
  VoicesDateTextFieldController([super._value]);
}

class VoicesDateTextField extends StatefulWidget {
  final VoicesDateTextFieldController controller;

  const VoicesDateTextField({
    super.key,
    required this.controller,
  });

  @override
  State<VoicesDateTextField> createState() => _VoicesDateTextFieldState();
}

class _VoicesDateTextFieldState extends State<VoicesDateTextField> {
  late final TextEditingController _textEditingController;

  @override
  void initState() {
    super.initState();
    _textEditingController = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    return VoicesTextField(
      controller: _textEditingController,
      onFieldSubmitted: (value) {},
    );
  }
  
  // listen _textEditingController, validate, parse and pass result to widget.controller
}

// voices_time_of_day_text_field.dart

class VoicesTimeOfDayTextFieldController extends ValueNotifier<TimeOfDay?> {
  VoicesTimeOfDayTextFieldController([super._value]);
}

class VoicesTimeOfDayTextField extends StatefulWidget {
  const VoicesTimeOfDayTextField({super.key});

  @override
  State<VoicesTimeOfDayTextField> createState() =>
      _VoicesTimeOfDayTextFieldState();
}

class _VoicesTimeOfDayTextFieldState extends State<VoicesTimeOfDayTextField> {
  @override
  Widget build(BuildContext context) {
    return VoicesTextField(
      onFieldSubmitted: (value) {},
    );
  }
}

class VoicesDatePicker extends StatefulWidget {
final DatePickerController controller;
final String timeZone;
const VoicesDatePicker({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure this will be harded to implement then what i just shown so we may discuss this with frontend team before committing to it

Copy link
Contributor

Test Report | ${\color{lightgreen}Pass: 343/343}$ | ${\color{red}Fail: 0/343}$ |

Copy link
Contributor

Test Report | ${\color{lightgreen}Pass: 343/343}$ | ${\color{red}Fail: 0/343}$ |

Copy link
Contributor

Test Report | ${\color{lightgreen}Pass: 346/346}$ | ${\color{red}Fail: 0/346}$ |

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dart Pull requests that update Dart code review me PR is ready for review ux/ui UX/UI related issues
Projects
Status: 🔖 Ready
Development

Successfully merging this pull request may close these issues.

🛠️ [TASK] : Date & Time input
3 participants