From f2a4ba66ebf3612a0c988aa020b2b0d51b9a6713 Mon Sep 17 00:00:00 2001 From: Yasien Mac Mini Date: Tue, 10 Jun 2025 12:36:06 +0200 Subject: [PATCH] new date and time fields --- .../mih_date_field.dart | 180 +++++++++++++++++ .../mih_time_field.dart | 190 ++++++++++++++++++ 2 files changed, 370 insertions(+) create mode 100644 Frontend/lib/mih_components/mih_package_components/mih_date_field.dart create mode 100644 Frontend/lib/mih_components/mih_package_components/mih_time_field.dart diff --git a/Frontend/lib/mih_components/mih_package_components/mih_date_field.dart b/Frontend/lib/mih_components/mih_package_components/mih_date_field.dart new file mode 100644 index 00000000..e89b4608 --- /dev/null +++ b/Frontend/lib/mih_components/mih_package_components/mih_date_field.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:mzansi_innovation_hub/main.dart'; + +class MihDateField extends StatefulWidget { + final TextEditingController controller; + final String labelText; + final bool required; + final double? width; + final double? height; + final double? borderRadius; + final double? elevation; + final FormFieldValidator? validator; + const MihDateField({ + super.key, + required this.controller, + required this.labelText, + required this.required, + this.width, + this.height, + this.borderRadius, + this.elevation, + this.validator, + }); + + @override + State createState() => _MihDateFieldState(); +} + +class _MihDateFieldState extends State { + FormFieldState? _formFieldState; + + Future _selectDate(BuildContext context) async { + DateTime? picked = await showDatePicker( + context: context, + initialDate: widget.controller.text.isNotEmpty + ? DateTime.tryParse(widget.controller.text) ?? DateTime.now() + : DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime(2100), + ); + if (picked != null) { + widget.controller.text = picked.toString().split(" ")[0]; + _formFieldState?.didChange(widget.controller.text); + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + final theme = MzanziInnovationHub.of(context)!.theme; + return Center( + child: SizedBox( + width: widget.width, + height: widget.height, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.labelText, + style: TextStyle( + color: theme.secondaryColor(), + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + if (!widget.required) + Text( + "(Optional)", + style: TextStyle( + color: theme.secondaryColor(), + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 4), + FormField( + initialValue: widget.controller.text, + validator: widget.validator, + autovalidateMode: AutovalidateMode.onUserInteraction, + builder: (field) { + _formFieldState = field; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Material( + elevation: widget.elevation ?? 4.0, + borderRadius: + BorderRadius.circular(widget.borderRadius ?? 8.0), + child: TextFormField( + controller: widget.controller, + readOnly: true, + onTap: () => _selectDate(context), + style: TextStyle( + color: theme.primaryColor(), + fontWeight: FontWeight.w500, + ), + decoration: InputDecoration( + suffixIcon: Icon( + Icons.calendar_today, + color: theme.primaryColor(), + ), + errorStyle: const TextStyle(height: 0, fontSize: 0), + contentPadding: const EdgeInsets.symmetric( + horizontal: 10.0, vertical: 8.0), + filled: true, + fillColor: theme.secondaryColor(), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: field.hasError + ? BorderSide( + color: theme.errorColor(), + width: 2.0, + ) + : BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: BorderSide( + color: field.hasError + ? theme.errorColor() + : theme.secondaryColor(), + width: 3.0, + ), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: BorderSide( + color: theme.errorColor(), + width: 3.0, + ), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: BorderSide( + color: theme.errorColor(), + width: 3.0, + ), + ), + ), + onChanged: (value) { + field.didChange(value); + }, + ), + ), + if (field.hasError) + Padding( + padding: const EdgeInsets.only(left: 8.0, top: 4.0), + child: Text( + field.errorText ?? '', + style: TextStyle( + fontSize: 12, + color: theme.errorColor(), + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/Frontend/lib/mih_components/mih_package_components/mih_time_field.dart b/Frontend/lib/mih_components/mih_package_components/mih_time_field.dart new file mode 100644 index 00000000..89ddc0ca --- /dev/null +++ b/Frontend/lib/mih_components/mih_package_components/mih_time_field.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import '../../main.dart'; + +class MihTimeField extends StatefulWidget { + final TextEditingController controller; + final String labelText; + final bool required; + final double? width; + final double? height; + final double? borderRadius; + final double? elevation; + final FormFieldValidator? validator; + + const MihTimeField({ + super.key, + required this.controller, + required this.labelText, + required this.required, + this.width, + this.height, + this.borderRadius, + this.elevation, + this.validator, + }); + + @override + State createState() => _MihTimeFieldState(); +} + +class _MihTimeFieldState extends State { + FormFieldState? _formFieldState; + + Future _selectTime(BuildContext context) async { + TimeOfDay? picked = await showTimePicker( + context: context, + initialTime: widget.controller.text.isNotEmpty + ? TimeOfDay( + hour: int.tryParse(widget.controller.text.split(":")[0]) ?? 0, + minute: int.tryParse(widget.controller.text.split(":")[1]) ?? 0, + ) + : TimeOfDay.now(), + builder: (context, child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true), + child: child as Widget, + ); + }, + ); + if (picked != null) { + final hours = picked.hour.toString().padLeft(2, '0'); + final minutes = picked.minute.toString().padLeft(2, '0'); + widget.controller.text = "$hours:$minutes"; + _formFieldState?.didChange(widget.controller.text); + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + final theme = MzanziInnovationHub.of(context)!.theme; + return Center( + child: SizedBox( + width: widget.width, + height: widget.height, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.labelText, + style: TextStyle( + color: theme.secondaryColor(), + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + if (!widget.required) + Text( + "(Optional)", + style: TextStyle( + color: theme.secondaryColor(), + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 4), + FormField( + initialValue: widget.controller.text, + validator: widget.validator, + autovalidateMode: AutovalidateMode.onUserInteraction, + builder: (field) { + _formFieldState = field; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Material( + elevation: widget.elevation ?? 4.0, + borderRadius: + BorderRadius.circular(widget.borderRadius ?? 8.0), + child: TextFormField( + controller: widget.controller, + readOnly: true, + onTap: () => _selectTime(context), + style: TextStyle( + color: theme.primaryColor(), + fontWeight: FontWeight.w500, + ), + decoration: InputDecoration( + suffixIcon: Icon( + Icons.access_time, + color: theme.primaryColor(), + ), + errorStyle: const TextStyle(height: 0, fontSize: 0), + contentPadding: const EdgeInsets.symmetric( + horizontal: 10.0, vertical: 8.0), + filled: true, + fillColor: theme.secondaryColor(), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: field.hasError + ? BorderSide( + color: theme.errorColor(), + width: 2.0, + ) + : BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: BorderSide( + color: field.hasError + ? theme.errorColor() + : theme.secondaryColor(), + width: 3.0, + ), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: BorderSide( + color: theme.errorColor(), + width: 3.0, + ), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + widget.borderRadius ?? 8.0), + borderSide: BorderSide( + color: theme.errorColor(), + width: 3.0, + ), + ), + ), + onChanged: (value) { + field.didChange(value); + }, + ), + ), + if (field.hasError) + Padding( + padding: const EdgeInsets.only(left: 8.0, top: 4.0), + child: Text( + field.errorText ?? '', + style: TextStyle( + fontSize: 12, + color: theme.errorColor(), + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), + ); + } +}