From 28af4fc51d5f2ea4040ba4e2e4b1e7e7e5235ebb Mon Sep 17 00:00:00 2001 From: Aakash S/O Rengarajan Ramaswamy Date: Wed, 25 Dec 2024 22:14:49 +0800 Subject: [PATCH 1/3] Revamp visuals for add conduct page --- contacts_app/.dart_tool/package_config.json | 2 +- contacts_app/.flutter-plugins-dependencies | 2 +- .../.dart_tool/package_config.json | 2 +- .../.flutter-plugins-dependencies | 2 +- .../.dart_tool/package_config.json | 2 +- .../pages/add_conduct_screen.dart | 536 +++++++++++++++--- .../widgets/add_conduct_button.dart | 47 +- .../widgets/participant_selector.dart | 230 ++++++-- 8 files changed, 689 insertions(+), 134 deletions(-) diff --git a/contacts_app/.dart_tool/package_config.json b/contacts_app/.dart_tool/package_config.json index 25161701..f5edbbeb 100644 --- a/contacts_app/.dart_tool/package_config.json +++ b/contacts_app/.dart_tool/package_config.json @@ -230,7 +230,7 @@ "languageVersion": "2.19" } ], - "generated": "2024-12-25T05:25:15.542922Z", + "generated": "2024-12-25T13:42:55.063839Z", "generator": "pub", "generatorVersion": "3.6.0", "flutterRoot": "file:///C:/Users/butte/OneDrive/Documents/flutter", diff --git a/contacts_app/.flutter-plugins-dependencies b/contacts_app/.flutter-plugins-dependencies index d9549f75..d33c3b12 100644 --- a/contacts_app/.flutter-plugins-dependencies +++ b/contacts_app/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"contacts_service","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\contacts_service-0.6.3\\\\","native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_apple-9.1.4\\\\","native_build":true,"dependencies":[]}],"android":[{"name":"contacts_service","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\contacts_service-0.6.3\\\\","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_android-10.3.6\\\\","native_build":true,"dependencies":[]}],"macos":[],"linux":[],"windows":[{"name":"permission_handler_windows","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_windows-0.1.3\\\\","native_build":true,"dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"contacts_service","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]}],"date_created":"2024-12-25 13:25:15.639258","version":"3.27.1","swift_package_manager_enabled":false} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"contacts_service","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\contacts_service-0.6.3\\\\","native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_apple-9.1.4\\\\","native_build":true,"dependencies":[]}],"android":[{"name":"contacts_service","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\contacts_service-0.6.3\\\\","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_android-10.3.6\\\\","native_build":true,"dependencies":[]}],"macos":[],"linux":[],"windows":[{"name":"permission_handler_windows","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_windows-0.1.3\\\\","native_build":true,"dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"contacts_service","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]}],"date_created":"2024-12-25 21:42:55.183003","version":"3.27.1","swift_package_manager_enabled":false} \ No newline at end of file diff --git a/firebase_project_1/.dart_tool/package_config.json b/firebase_project_1/.dart_tool/package_config.json index 5eeb1d99..aba4eca2 100644 --- a/firebase_project_1/.dart_tool/package_config.json +++ b/firebase_project_1/.dart_tool/package_config.json @@ -398,7 +398,7 @@ "languageVersion": "2.19" } ], - "generated": "2024-12-25T05:25:17.098860Z", + "generated": "2024-12-25T13:42:56.972169Z", "generator": "pub", "generatorVersion": "3.6.0", "flutterRoot": "file:///C:/Users/butte/OneDrive/Documents/flutter", diff --git a/firebase_project_1/.flutter-plugins-dependencies b/firebase_project_1/.flutter-plugins-dependencies index 0ae356ad..6b877bfe 100644 --- a/firebase_project_1/.flutter-plugins-dependencies +++ b/firebase_project_1/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"cloud_firestore","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore-4.14.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_auth","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth-4.16.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core-2.24.2\\\\","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.0\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"cloud_firestore","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore-4.14.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_auth","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth-4.16.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core-2.24.2\\\\","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.9\\\\","native_build":true,"dependencies":[]}],"macos":[{"name":"cloud_firestore","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore-4.14.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_auth","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth-4.16.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core-2.24.2\\\\","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.0\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[]}],"windows":[{"name":"cloud_firestore","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore-4.14.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_auth","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth-4.16.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core-2.24.2\\\\","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[]}],"web":[{"name":"cloud_firestore_web","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore_web-3.9.0\\\\","dependencies":["firebase_core_web"]},{"name":"firebase_auth_web","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth_web-5.8.13\\\\","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core_web-2.10.0\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"cloud_firestore","dependencies":["cloud_firestore_web","firebase_core"]},{"name":"cloud_firestore_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_auth","dependencies":["firebase_auth_web","firebase_core"]},{"name":"firebase_auth_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2024-12-25 13:25:17.258997","version":"3.27.1","swift_package_manager_enabled":false} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"cloud_firestore","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore-4.14.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_auth","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth-4.16.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core-2.24.2\\\\","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.0\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"cloud_firestore","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore-4.14.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_auth","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth-4.16.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core-2.24.2\\\\","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.9\\\\","native_build":true,"dependencies":[]}],"macos":[{"name":"cloud_firestore","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore-4.14.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_auth","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth-4.16.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core-2.24.2\\\\","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.0\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[]}],"windows":[{"name":"cloud_firestore","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore-4.14.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_auth","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth-4.16.0\\\\","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core-2.24.2\\\\","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[]}],"web":[{"name":"cloud_firestore_web","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\cloud_firestore_web-3.9.0\\\\","dependencies":["firebase_core_web"]},{"name":"firebase_auth_web","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_auth_web-5.8.13\\\\","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","path":"C:\\\\Users\\\\butte\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\firebase_core_web-2.10.0\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"cloud_firestore","dependencies":["cloud_firestore_web","firebase_core"]},{"name":"cloud_firestore_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_auth","dependencies":["firebase_auth_web","firebase_core"]},{"name":"firebase_auth_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2024-12-25 21:42:57.120002","version":"3.27.1","swift_package_manager_enabled":false} \ No newline at end of file diff --git a/firebase_project_2/.dart_tool/package_config.json b/firebase_project_2/.dart_tool/package_config.json index 9c5420de..186dae82 100644 --- a/firebase_project_2/.dart_tool/package_config.json +++ b/firebase_project_2/.dart_tool/package_config.json @@ -986,7 +986,7 @@ "languageVersion": "2.19" } ], - "generated": "2024-12-25T05:25:20.221695Z", + "generated": "2024-12-25T13:43:00.326167Z", "generator": "pub", "generatorVersion": "3.6.0", "flutterRoot": "file:///C:/Users/butte/OneDrive/Documents/flutter", diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/add_conduct_screen.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/add_conduct_screen.dart index d99ada68..c5bc2c01 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/add_conduct_screen.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/add_conduct_screen.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; +import 'package:google_fonts/google_fonts.dart'; import '../../domain/entities/conduct.dart'; import '../providers/conduct_provider.dart'; import '../widgets/participant_selector.dart'; class AddConductScreen extends StatefulWidget { - final Conduct? conduct; // Optional - if provided, we're in edit mode + final Conduct? conduct; const AddConductScreen({super.key, this.conduct}); @@ -23,7 +25,6 @@ class _AddConductScreenState extends State { late String _endTime; List _selectedParticipants = []; Map _soldierReason = {}; - List _nonParticipants = []; final List _conductTypes = [ 'Run', @@ -44,8 +45,8 @@ class _AddConductScreenState extends State { _conductNameController = TextEditingController(text: widget.conduct?.conductName); _selectedConductType = widget.conduct?.conductType; _startDate = widget.conduct?.startDate ?? DateFormat('d MMM yyyy').format(DateTime.now()); - _startTime = widget.conduct?.startTime ?? 'Start Time:'; - _endTime = widget.conduct?.endTime ?? 'End Time:'; + _startTime = widget.conduct?.startTime ?? DateFormat('HH:mm').format(DateTime.now()); + _endTime = widget.conduct?.endTime ?? DateFormat('HH:mm').format(DateTime.now().add(const Duration(hours: 1))); _selectedParticipants = widget.conduct?.participants ?? []; _soldierReason = widget.conduct?.soldierReason ?? {}; } @@ -62,6 +63,27 @@ class _AddConductScreenState extends State { initialDate: DateFormat('d MMM yyyy').parse(_startDate), firstDate: DateTime(2022), lastDate: DateTime(2025), + builder: (context, child) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + return Theme( + data: Theme.of(context).copyWith( + colorScheme: theme.colorScheme.copyWith( + primary: theme.colorScheme.secondary, + onPrimary: Colors.white, + surface: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, + onSurface: isDarkMode ? Colors.white : theme.colorScheme.onSurface, + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: theme.colorScheme.secondary, + ), + ), + dialogBackgroundColor: isDarkMode ? const Color.fromARGB(255, 35, 40, 55) : Colors.white, + ), + child: child!, + ); + }, ); if (picked != null) { setState(() { @@ -74,6 +96,27 @@ class _AddConductScreenState extends State { final TimeOfDay? picked = await showTimePicker( context: context, initialTime: TimeOfDay.now(), + builder: (context, child) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + return Theme( + data: Theme.of(context).copyWith( + colorScheme: theme.colorScheme.copyWith( + primary: theme.colorScheme.secondary, + onPrimary: Colors.white, + surface: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, + onSurface: isDarkMode ? Colors.white : theme.colorScheme.onSurface, + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: theme.colorScheme.secondary, + ), + ), + dialogBackgroundColor: isDarkMode ? const Color.fromARGB(255, 35, 40, 55) : Colors.white, + ), + child: child!, + ); + }, ); if (picked != null) { setState(() { @@ -92,9 +135,6 @@ class _AddConductScreenState extends State { setState(() { _selectedParticipants = participants; _soldierReason = reasons; - _nonParticipants = allSoldiers - .where((id) => !participants.contains(id)) - .toList(); }); } @@ -134,81 +174,421 @@ class _AddConductScreenState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + return Scaffold( - appBar: AppBar( - title: Text(widget.conduct != null ? 'Edit Conduct' : 'Add New Conduct'), - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16.0), - child: Form( - key: _formKey, + backgroundColor: isDarkMode ? const Color.fromARGB(255, 35, 40, 55) : theme.colorScheme.surface, + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + isDarkMode ? const Color.fromARGB(255, 35, 40, 55) : Colors.white, + isDarkMode ? const Color.fromARGB(255, 25, 30, 45) : Colors.white, + ], + ), + ), + child: SafeArea( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextFormField( - controller: _conductNameController, - decoration: const InputDecoration(labelText: 'Conduct Name'), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a conduct name'; - } - return null; - }, - ), - const SizedBox(height: 16), - DropdownButtonFormField( - value: _selectedConductType, - decoration: const InputDecoration(labelText: 'Conduct Type'), - items: _conductTypes.map((type) { - return DropdownMenuItem(value: type, child: Text(type)); - }).toList(), - onChanged: (value) { - setState(() { - _selectedConductType = value; - }); - }, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please select a conduct type'; - } - return null; - }, - ), - const SizedBox(height: 16), - ListTile( - title: Text(_startDate), - trailing: const Icon(Icons.calendar_today), - onTap: () => _selectDate(context), - ), - ListTile( - title: Text(_startTime), - trailing: const Icon(Icons.access_time), - onTap: () => _selectTime(context, true), - ), - ListTile( - title: Text(_endTime), - trailing: const Icon(Icons.access_time), - onTap: () => _selectTime(context, false), - ), - const SizedBox(height: 16), - ParticipantSelector( - selectedParticipants: _selectedParticipants, - soldierReason: _soldierReason, - conductType: _selectedConductType, - onParticipantsChanged: (participants, reasons, nonParticipants) { - setState(() { - _selectedParticipants = participants; - _soldierReason = reasons; - _nonParticipants = nonParticipants; - }); - }, + Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + theme.colorScheme.secondary, + theme.colorScheme.secondary.withOpacity(0.9), + ], + ), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + padding: EdgeInsets.all(8.w), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.arrow_back_rounded, + color: Colors.white, + size: 20.sp, + ), + ), + ), + SizedBox(width: 16.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.conduct != null ? 'Edit Conduct' : 'Add New Conduct', + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 20.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + Text( + 'Fill in the details for the conduct', + style: GoogleFonts.poppins( + color: Colors.white70, + fontSize: 12.sp, + letterSpacing: 0.5, + ), + ), + ], + ), + ], + ), ), - const SizedBox(height: 32), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _saveConduct, - child: Text(widget.conduct != null ? 'Update Conduct' : 'Add Conduct'), + Expanded( + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + color: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], + ), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Status Type', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 8.h), + Container( + decoration: BoxDecoration( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100], + borderRadius: BorderRadius.circular(8.r), + ), + child: DropdownButtonFormField( + value: _selectedConductType, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + ), + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric(horizontal: 12.w), + border: InputBorder.none, + hintText: 'Select status type...', + hintStyle: GoogleFonts.poppins( + color: isDarkMode ? Colors.white38 : Colors.black38, + fontSize: 14.sp, + ), + ), + items: _conductTypes.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + setState(() { + _selectedConductType = value; + }); + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please select a conduct type'; + } + return null; + }, + ), + ), + SizedBox(height: 16.h), + Text( + 'Status Name', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 8.h), + Container( + decoration: BoxDecoration( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100], + borderRadius: BorderRadius.circular(8.r), + ), + child: TextFormField( + controller: _conductNameController, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + ), + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), + border: InputBorder.none, + hintText: 'Enter status name...', + hintStyle: GoogleFonts.poppins( + color: isDarkMode ? Colors.white38 : Colors.black38, + fontSize: 14.sp, + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a conduct name'; + } + return null; + }, + ), + ), + ], + ), + ), + SizedBox(height: 16.h), + Container( + decoration: BoxDecoration( + color: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], + ), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Date and Time', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 12.h), + GestureDetector( + onTap: () => _selectDate(context), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100], + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _startDate, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + ), + ), + Icon( + Icons.calendar_today_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : theme.colorScheme.tertiary, + size: 20.sp, + ), + ], + ), + ), + ), + SizedBox(height: 12.h), + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () => _selectTime(context, true), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100], + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _startTime, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + ), + ), + Icon( + Icons.access_time_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : theme.colorScheme.tertiary, + size: 20.sp, + ), + ], + ), + ), + ), + ), + SizedBox(width: 12.w), + Expanded( + child: GestureDetector( + onTap: () => _selectTime(context, false), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100], + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _endTime, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + ), + ), + Icon( + Icons.access_time_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : theme.colorScheme.tertiary, + size: 20.sp, + ), + ], + ), + ), + ), + ), + ], + ), + ], + ), + ), + SizedBox(height: 16.h), + Container( + decoration: BoxDecoration( + color: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], + ), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Participants', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 12.h), + ParticipantSelector( + selectedParticipants: _selectedParticipants, + soldierReason: _soldierReason, + conductType: _selectedConductType, + onParticipantsChanged: (participants, reasons, nonParticipants) { + setState(() { + _selectedParticipants = participants; + _soldierReason = reasons; + }); + }, + ), + ], + ), + ), + SizedBox(height: 24.h), + Container( + width: double.infinity, + height: 48.h, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + theme.colorScheme.secondary, + theme.colorScheme.secondary.withOpacity(0.9), + ], + ), + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: theme.colorScheme.secondary.withOpacity(0.3), + blurRadius: 12.r, + offset: Offset(0, 6.h), + ), + ], + ), + child: ElevatedButton.icon( + onPressed: _saveConduct, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + ), + icon: Icon( + widget.conduct != null ? Icons.save_rounded : Icons.add_rounded, + size: 20.sp, + color: Colors.white, + ), + label: Text( + widget.conduct != null ? 'UPDATE CONDUCT' : 'ADD CONDUCT', + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + letterSpacing: 1, + ), + ), + ), + ), + ], + ), + ), ), ), ], diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/add_conduct_button.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/add_conduct_button.dart index 16cd7142..acc1ee1d 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/add_conduct_button.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/add_conduct_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../pages/add_conduct_screen.dart'; class AddConductButton extends StatelessWidget { @@ -6,16 +7,44 @@ class AddConductButton extends StatelessWidget { @override Widget build(BuildContext context) { - return FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AddConductScreen(), + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + theme.colorScheme.secondary, + theme.colorScheme.secondary.withOpacity(0.9), + ], + ), + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: theme.colorScheme.secondary.withOpacity(isDarkMode ? 0.2 : 0.3), + blurRadius: 16.r, + offset: Offset(0, 6.h), + spreadRadius: 2.r, ), - ); - }, - child: const Icon(Icons.add), + ], + ), + child: FloatingActionButton( + backgroundColor: Colors.transparent, + elevation: 0, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AddConductScreen(), + ), + ); + }, + child: Icon( + Icons.add_rounded, + color: Colors.white, + size: 24.sp, + ), + ), ); } } \ No newline at end of file diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/participant_selector.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/participant_selector.dart index 7d1a9b62..f204247b 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/participant_selector.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/participant_selector.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; import '../../domain/usecases/filter_participants_usecase.dart'; import '../providers/conduct_provider.dart'; @@ -25,6 +27,15 @@ class ParticipantSelector extends StatefulWidget { class _ParticipantSelectorState extends State { late List _selectedParticipants; late Map _soldierReason; + List> _allSoldiers = []; + String _searchQuery = ''; + List _nonParticipants = []; + + @override + void initState() { + super.initState(); + _loadSoldiers(); + } @override void didUpdateWidget(ParticipantSelector oldWidget) { @@ -58,16 +69,6 @@ class _ParticipantSelectorState extends State { ); } - List> _allSoldiers = []; - String _searchQuery = ''; - List _nonParticipants = []; - - @override - void initState() { - super.initState(); - _loadSoldiers(); - } - Future _loadSoldiers() async { final snapshot = await FirebaseFirestore.instance.collection('Users').get(); final soldiers = snapshot.docs @@ -119,51 +120,92 @@ class _ParticipantSelectorState extends State { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextField( - decoration: const InputDecoration( - labelText: 'Search Soldiers', - prefixIcon: Icon(Icons.search), + Container( + decoration: BoxDecoration( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100], + borderRadius: BorderRadius.circular(8.r), + ), + child: TextField( + onChanged: (value) { + setState(() { + _searchQuery = value; + }); + }, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + ), + decoration: InputDecoration( + hintText: 'Search Soldiers', + hintStyle: GoogleFonts.poppins( + color: isDarkMode ? Colors.white38 : Colors.black38, + fontSize: 14.sp, + ), + prefixIcon: Icon( + Icons.search_rounded, + color: isDarkMode ? Colors.white38 : Colors.black38, + size: 20.sp, + ), + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), + ), ), - onChanged: (value) { - setState(() { - _searchQuery = value; - }); - }, ), - const SizedBox(height: 16), + SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( + Text( 'Select Participants', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + fontWeight: FontWeight.w600, ), ), TextButton( onPressed: () { if (widget.selectedParticipants.length == _allSoldiers.length) { - // Deselect all widget.onParticipantsChanged([], {}, []); } else { - // Select all final allSoldierIds = _allSoldiers.map((s) => s['id'].toString()).toList(); widget.onParticipantsChanged(allSoldierIds, {}, []); } }, - child: Text( - widget.selectedParticipants.length == _allSoldiers.length - ? 'Deselect All' - : 'Select All', + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: isDarkMode + ? theme.colorScheme.secondary.withOpacity(0.15) + : theme.colorScheme.secondary.withOpacity(0.1), + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + widget.selectedParticipants.length == _allSoldiers.length + ? 'DESELECT ALL' + : 'SELECT ALL', + style: GoogleFonts.poppins( + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : theme.colorScheme.secondary, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), ), ), ], ), - const SizedBox(height: 8), + SizedBox(height: 12.h), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -171,18 +213,122 @@ class _ParticipantSelectorState extends State { itemBuilder: (context, index) { final soldier = _filteredSoldiers[index]; final isSelected = widget.selectedParticipants.contains(soldier['id']); - - return ListTile( - title: Text(soldier['name']), - subtitle: Text(soldier['rank']), - trailing: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: isSelected ? Colors.red : Colors.green, + + return Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + isSelected + ? theme.colorScheme.secondary.withOpacity(isDarkMode ? 0.3 : 0.1) + : isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100]!, + isSelected + ? theme.colorScheme.secondary.withOpacity(isDarkMode ? 0.1 : 0.05) + : isDarkMode + ? Colors.black.withOpacity(0.1) + : Colors.grey[50]!, + ], + ), + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: isSelected + ? theme.colorScheme.secondary.withOpacity(isDarkMode ? 0.5 : 0.3) + : Colors.transparent, + width: 1, + ), ), - onPressed: () => _toggleParticipant(soldier['id']), - child: Text( - isSelected ? 'REMOVE' : 'ADD', - style: const TextStyle(color: Colors.white), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => _toggleParticipant(soldier['id']), + borderRadius: BorderRadius.circular(12.r), + child: Padding( + padding: EdgeInsets.all(12.w), + child: Row( + children: [ + Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + color: isSelected + ? theme.colorScheme.secondary.withOpacity(0.9) + : isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.white, + borderRadius: BorderRadius.circular(8.r), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Center( + child: Image.asset( + "lib/assets/army-ranks/${soldier['rank'].toString().toLowerCase()}.png", + width: 24.w, + height: 24.w, + color: isSelected + ? Colors.white + : isDarkMode + ? Colors.white + : Colors.black87, + ), + ), + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + soldier['name'], + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + Text( + soldier['rank'], + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: isSelected + ? Colors.red.withOpacity(0.15) + : theme.colorScheme.secondary.withOpacity(0.15), + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + isSelected ? 'REMOVE' : 'ADD', + style: GoogleFonts.poppins( + color: isSelected + ? Colors.red.shade400 + : isDarkMode + ? theme.colorScheme.secondary.withOpacity(0.95) + : theme.colorScheme.secondary.withOpacity(0.9), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + ), + ], + ), + ), + ), ), ), ); From ff9e87e141a7368aa5c5ba358fbb49d4cc4066b9 Mon Sep 17 00:00:00 2001 From: Aakash S/O Rengarajan Ramaswamy Date: Thu, 26 Dec 2024 00:49:44 +0800 Subject: [PATCH 2/3] Complete visual overhaul of the main conduct tracker page with changes made to the add conduct page, edit conduct page and the details page to fit the current style of the app --- .../domain/entities/conduct.dart | 63 +- .../pages/add_conduct_screen.dart | 8 +- .../pages/conduct_details_screen.dart | 649 +++++++++++++++--- .../pages/conduct_tracker_screen.dart | 269 ++++++-- .../widgets/add_conduct_button.dart | 35 +- .../widgets/conduct_bar_graph.dart | 352 ++++++++-- .../widgets/conduct_calendar.dart | 238 +++++-- .../presentation/widgets/conduct_list.dart | 178 ++++- .../presentation/widgets/conduct_tile.dart | 135 +++- .../presentation/widgets/date_selector.dart | 122 +++- .../widgets/no_conducts_widget.dart | 72 +- .../pages/nominal_roll_screen.dart | 173 ++--- 12 files changed, 1842 insertions(+), 452 deletions(-) diff --git a/trooptrak_final_application/lib/features/conduct_tracker/domain/entities/conduct.dart b/trooptrak_final_application/lib/features/conduct_tracker/domain/entities/conduct.dart index fdcf3f94..8b129e08 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/domain/entities/conduct.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/domain/entities/conduct.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + class Conduct { final String id; final String conductName; @@ -6,10 +8,10 @@ class Conduct { final String startTime; final String endTime; final List participants; - final List nonParticipants; // Add this + final List nonParticipants; final Map soldierReason; - Conduct({ + const Conduct({ required this.id, required this.conductName, required this.conductType, @@ -17,7 +19,62 @@ class Conduct { required this.startTime, required this.endTime, required this.participants, - required this.nonParticipants, // Add this + required this.nonParticipants, required this.soldierReason, }); + + static IconData getIconForConductType(String conductType) { + switch (conductType.toLowerCase()) { + case 'run': + return Icons.directions_run_rounded; + case 's&p': + return Icons.fitness_center_rounded; + case 'imt': + return Icons.gps_fixed_rounded; + case 'atp': + return Icons.adjust_rounded; + case 'ippt': + return Icons.military_tech_rounded; + case 'soc': + return Icons.park_rounded; + case 'metabolic circuit': + return Icons.flash_on_rounded; + case 'combat circuit': + return Icons.shield_rounded; + case 'route march': + return Icons.hiking_rounded; + case 'outfield': + return Icons.forest_rounded; + default: + return Icons.event_rounded; + } + } + + Map toMap() { + return { + 'id': id, + 'conductName': conductName, + 'conductType': conductType, + 'startDate': startDate, + 'startTime': startTime, + 'endTime': endTime, + 'participants': participants, + 'nonParticipants': nonParticipants, + 'soldierReason': soldierReason, + }; + } + + factory Conduct.fromMap(Map map) { + return Conduct( + id: map['id'] ?? '', + conductName: map['conductName'] ?? '', + conductType: map['conductType'] ?? '', + startDate: map['startDate'] ?? '', + startTime: map['startTime'] ?? '', + endTime: map['endTime'] ?? '', + participants: List.from(map['participants'] ?? []), + nonParticipants: List.from(map['nonParticipants'] ?? []), + soldierReason: Map.from(map['soldierReason'] ?? {}), + ); + } } \ No newline at end of file diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/add_conduct_screen.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/add_conduct_screen.dart index c5bc2c01..8e4ce2b4 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/add_conduct_screen.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/add_conduct_screen.dart @@ -273,7 +273,7 @@ class _AddConductScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Status Type', + 'Conduct Type', style: GoogleFonts.poppins( color: isDarkMode ? Colors.white70 : Colors.black54, fontSize: 12.sp, @@ -297,7 +297,7 @@ class _AddConductScreenState extends State { decoration: InputDecoration( contentPadding: EdgeInsets.symmetric(horizontal: 12.w), border: InputBorder.none, - hintText: 'Select status type...', + hintText: 'Select conduct type...', hintStyle: GoogleFonts.poppins( color: isDarkMode ? Colors.white38 : Colors.black38, fontSize: 14.sp, @@ -324,7 +324,7 @@ class _AddConductScreenState extends State { ), SizedBox(height: 16.h), Text( - 'Status Name', + 'Conduct Name', style: GoogleFonts.poppins( color: isDarkMode ? Colors.white70 : Colors.black54, fontSize: 12.sp, @@ -348,7 +348,7 @@ class _AddConductScreenState extends State { decoration: InputDecoration( contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), border: InputBorder.none, - hintText: 'Enter status name...', + hintText: 'Enter conduct name...', hintStyle: GoogleFonts.poppins( color: isDarkMode ? Colors.white38 : Colors.black38, fontSize: 14.sp, diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/conduct_details_screen.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/conduct_details_screen.dart index ef98d26b..8d356af4 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/conduct_details_screen.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/conduct_details_screen.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; import '../../domain/entities/conduct.dart'; import '../providers/conduct_provider.dart'; +import '../../../../features/nominal_roll/presentation/providers/user_detail_provider.dart'; +import '../../../../features/nominal_roll/domain/entities/user.dart'; import 'add_conduct_screen.dart'; -class ConductDetailsScreen extends StatelessWidget { +class ConductDetailsScreen extends StatefulWidget { final String conductId; const ConductDetailsScreen({ @@ -12,16 +16,67 @@ class ConductDetailsScreen extends StatelessWidget { required this.conductId, }); + @override + State createState() => _ConductDetailsScreenState(); +} + +class _ConductDetailsScreenState extends State { + final TextEditingController _participantsSearchController = TextEditingController(); + final TextEditingController _nonParticipantsSearchController = TextEditingController(); + final FocusNode _participantsFocusNode = FocusNode(); + final FocusNode _nonParticipantsFocusNode = FocusNode(); + ValueNotifier _participantsSearchQuery = ValueNotifier(''); + ValueNotifier _nonParticipantsSearchQuery = ValueNotifier(''); + + @override + void dispose() { + _participantsSearchController.dispose(); + _nonParticipantsSearchController.dispose(); + _participantsFocusNode.dispose(); + _nonParticipantsFocusNode.dispose(); + _participantsSearchQuery.dispose(); + _nonParticipantsSearchQuery.dispose(); + super.dispose(); + } + void _showDeleteDialog(BuildContext context, String conductId) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Delete Conduct'), - content: const Text('Are you sure you want to delete this conduct?'), + backgroundColor: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + title: Text( + 'Delete Conduct', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 18.sp, + fontWeight: FontWeight.w600, + ), + ), + content: Text( + 'Are you sure you want to delete this conduct?', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 14.sp, + ), + ), actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text( + 'Cancel', + style: GoogleFonts.poppins( + color: theme.colorScheme.secondary, + fontWeight: FontWeight.w500, + ), + ), ), TextButton( onPressed: () { @@ -29,7 +84,13 @@ class ConductDetailsScreen extends StatelessWidget { Navigator.pop(context); // Close dialog Navigator.pop(context); // Go back to previous screen }, - child: const Text('Delete'), + child: Text( + 'Delete', + style: GoogleFonts.poppins( + color: Colors.red[400], + fontWeight: FontWeight.w600, + ), + ), ), ], ), @@ -37,143 +98,571 @@ class ConductDetailsScreen extends StatelessWidget { } Widget _buildInfoSection(BuildContext context, {required String title, required List children}) { - return Padding( - padding: const EdgeInsets.all(16.0), + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + + return Container( + margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, - style: Theme.of(context).textTheme.titleLarge, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + letterSpacing: 0.5, + ), ), - const SizedBox(height: 8), + SizedBox(height: 12.h), ...children, ], ), ); } - Widget _buildInfoRow(String label, String value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), + Widget _buildInfoRow(BuildContext context, String label, String value) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Container( + margin: EdgeInsets.only(bottom: 8.h), + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100], + borderRadius: BorderRadius.circular(12.r), + ), child: Row( children: [ Text( '$label: ', - style: const TextStyle(fontWeight: FontWeight.bold), + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + Text( + value, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), ), - Text(value), ], ), ); } + bool rankColorPicker(String rank) { + return [ + 'REC', + 'PTE', + 'LCP', + 'CPL', + 'CFC', + '3SG', + '2SG', + '1SG', + 'SSG', + 'MSG', + '3WO', + '2WO', + '1WO', + 'MWO', + 'SWO', + 'CWO' + ].contains(rank); + } + + Widget _buildSearchField({ + required TextEditingController controller, + required FocusNode focusNode, + required ValueNotifier searchQuery, + required String hintText, + required bool isDarkMode, + }) { + return Container( + margin: EdgeInsets.only(bottom: 16.h), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100], + borderRadius: BorderRadius.circular(12.r), + ), + child: Material( + color: Colors.transparent, + child: TextField( + controller: controller, + focusNode: focusNode, + onChanged: (value) => searchQuery.value = value, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + ), + decoration: InputDecoration( + hintText: hintText, + hintStyle: GoogleFonts.poppins( + color: isDarkMode ? Colors.white38 : Colors.black38, + fontSize: 14.sp, + ), + prefixIcon: Icon( + Icons.search_rounded, + color: isDarkMode ? Colors.white38 : Colors.black38, + size: 20.sp, + ), + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), + ), + ), + ), + ); + } + Widget _buildParticipantsSection(BuildContext context, Conduct conductData) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final theme = Theme.of(context); + return _buildInfoSection( context, title: 'Participants', children: [ + _buildSearchField( + controller: _participantsSearchController, + focusNode: _participantsFocusNode, + searchQuery: _participantsSearchQuery, + hintText: 'Search Soldiers', + isDarkMode: isDarkMode, + ), if (conductData.participants.isEmpty) - const Text('No participants') + Text( + 'No participants', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 14.sp, + fontStyle: FontStyle.italic, + ), + ) else - ...conductData.participants.map((participant) => Text(participant)), + ValueListenableBuilder( + valueListenable: _participantsSearchQuery, + builder: (context, searchQuery, _) { + final filteredParticipants = conductData.participants + .where((participant) => + participant.toLowerCase().contains(searchQuery.toLowerCase())) + .toList(); + + return Column( + children: filteredParticipants.map((participant) { + return StreamBuilder( + stream: context.read().getUserByIdUseCase(participant), + builder: (context, snapshot) { + final rank = snapshot.data?.rank ?? 'REC'; + + return Container( + margin: EdgeInsets.only(bottom: 8.h), + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + color: theme.colorScheme.secondary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: theme.colorScheme.secondary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + children: [ + Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + color: theme.colorScheme.secondary.withOpacity(0.9), + borderRadius: BorderRadius.circular(8.r), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Center( + child: Image.asset( + "lib/assets/army-ranks/${rank.toLowerCase()}.png", + width: 24.w, + height: 24.h, + color: rankColorPicker(rank) ? Colors.white : null, + ), + ), + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + participant, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + Text( + rank, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + ), + ), + ], + ), + ), + ], + ), + ); + }, + ); + }).toList(), + ); + }, + ), ], ); } Widget _buildNonParticipantsSection(BuildContext context, Conduct conductData) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final theme = Theme.of(context); + return _buildInfoSection( context, title: 'Non-Participants', children: [ + _buildSearchField( + controller: _nonParticipantsSearchController, + focusNode: _nonParticipantsFocusNode, + searchQuery: _nonParticipantsSearchQuery, + hintText: 'Search Non-Participants', + isDarkMode: isDarkMode, + ), if (conductData.nonParticipants.isEmpty) - const Text('No non-participants') + Text( + 'No non-participants', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 14.sp, + fontStyle: FontStyle.italic, + ), + ) else - ...conductData.nonParticipants.map((nonParticipant) => Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - nonParticipant, - style: const TextStyle(fontWeight: FontWeight.w500), - ), - ), - const SizedBox(width: 8), - Expanded( - child: Text( - 'Reason: ${conductData.soldierReason[nonParticipant] ?? 'No reason provided'}', - style: TextStyle( - color: Colors.grey[600], - fontStyle: FontStyle.italic, + ValueListenableBuilder( + valueListenable: _nonParticipantsSearchQuery, + builder: (context, searchQuery, _) { + final filteredNonParticipants = conductData.nonParticipants + .where((nonParticipant) => + nonParticipant.toLowerCase().contains(searchQuery.toLowerCase())) + .toList(); + + return Column( + children: filteredNonParticipants.map((nonParticipant) { + return StreamBuilder( + stream: context.read().getUserByIdUseCase(nonParticipant), + builder: (context, snapshot) { + final rank = snapshot.data?.rank ?? 'REC'; + + return Container( + margin: EdgeInsets.only(bottom: 8.h), + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: Colors.red.withOpacity(0.2), + width: 1, + ), ), - ), - ), - ], - ), - )), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + color: Colors.red[400], + borderRadius: BorderRadius.circular(8.r), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Center( + child: Image.asset( + "lib/assets/army-ranks/${rank.toLowerCase()}.png", + width: 24.w, + height: 24.h, + color: rankColorPicker(rank) ? Colors.white : null, + ), + ), + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + nonParticipant, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + Text( + rank, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + ), + ), + SizedBox(height: 4.h), + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(6.r), + border: Border.all( + color: Colors.red.withOpacity(0.2), + width: 1, + ), + ), + child: Text( + conductData.soldierReason[nonParticipant] ?? 'No reason provided', + style: GoogleFonts.poppins( + color: Colors.red[400], + fontSize: 12.sp, + fontStyle: FontStyle.italic, + ), + ), + ), + ], + ), + ), + ], + ), + ); + }, + ); + }).toList(), + ); + }, + ), ], ); } @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + return StreamBuilder( - stream: context.read().getConductById(conductId), + stream: context.read().getConductById(widget.conductId), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return const Scaffold( - body: Center(child: CircularProgressIndicator()), + return Scaffold( + backgroundColor: isDarkMode + ? const Color.fromARGB(255, 35, 40, 55) + : Colors.white, + body: Center( + child: CircularProgressIndicator( + color: theme.colorScheme.secondary, + ), + ), ); } if (!snapshot.hasData) { - return const Scaffold( - body: Center(child: Text('Conduct not found')), + return Scaffold( + backgroundColor: isDarkMode + ? const Color.fromARGB(255, 35, 40, 55) + : Colors.white, + body: Center( + child: Text( + 'Conduct not found', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 16.sp, + ), + ), + ), ); } final conductData = snapshot.data!; return Scaffold( - appBar: AppBar( - title: const Text('Conduct Details'), - actions: [ - IconButton( - icon: const Icon(Icons.edit), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AddConductScreen(conduct: conductData), - ), - ); - }, + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + isDarkMode + ? const Color.fromARGB(255, 35, 40, 55) + : Colors.white, + isDarkMode + ? const Color.fromARGB(255, 25, 30, 45) + : Colors.white, + ], ), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () => _showDeleteDialog(context, conductData.id), + ), + child: SafeArea( + child: Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + theme.colorScheme.secondary, + theme.colorScheme.secondary.withOpacity(0.9), + ], + ), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + padding: EdgeInsets.all(8.w), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.arrow_back_rounded, + color: Colors.white, + size: 20.sp, + ), + ), + ), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Conduct Details', + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 20.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + Text( + 'View and manage conduct information', + style: GoogleFonts.poppins( + color: Colors.white70, + fontSize: 12.sp, + letterSpacing: 0.5, + ), + ), + ], + ), + ), + Row( + children: [ + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AddConductScreen(conduct: conductData), + ), + ); + }, + child: Container( + padding: EdgeInsets.all(8.w), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.edit_rounded, + color: Colors.white, + size: 20.sp, + ), + ), + ), + SizedBox(width: 8.w), + GestureDetector( + onTap: () => _showDeleteDialog(context, conductData.id), + child: Container( + padding: EdgeInsets.all(8.w), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.2), + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.delete_rounded, + color: Colors.red[400], + size: 20.sp, + ), + ), + ), + ], + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(vertical: 16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoSection( + context, + title: 'Conduct Information', + children: [ + _buildInfoRow(context, 'Name', conductData.conductName), + _buildInfoRow(context, 'Type', conductData.conductType), + _buildInfoRow(context, 'Date', conductData.startDate), + _buildInfoRow(context, 'Time', '${conductData.startTime} - ${conductData.endTime}'), + ], + ), + _buildParticipantsSection(context, conductData), + _buildNonParticipantsSection(context, conductData), + SizedBox(height: 16.h), + ], + ), + ), + ), + ], ), - ], - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildInfoSection( - context, - title: 'Conduct Information', - children: [ - _buildInfoRow('Name', conductData.conductName), - _buildInfoRow('Type', conductData.conductType), - _buildInfoRow('Date', conductData.startDate), - _buildInfoRow('Time', '${conductData.startTime} - ${conductData.endTime}'), - ], - ), - _buildParticipantsSection(context, conductData), - _buildNonParticipantsSection(context, conductData), - ], ), ), ); diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/conduct_tracker_screen.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/conduct_tracker_screen.dart index f1e211db..46a6f92b 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/conduct_tracker_screen.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/pages/conduct_tracker_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; import '../providers/conduct_provider.dart'; import '../widgets/conduct_calendar.dart'; @@ -8,66 +10,235 @@ import '../widgets/date_selector.dart'; import '../widgets/add_conduct_button.dart'; import '../widgets/no_conducts_widget.dart'; import '../../domain/entities/conduct.dart'; +import '../../../../core/theme/theme_manager.dart'; -class ConductTrackerScreen extends StatelessWidget { +class ConductTrackerScreen extends StatefulWidget { const ConductTrackerScreen({super.key}); + @override + State createState() => _ConductTrackerScreenState(); +} + +class _ConductTrackerScreenState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + // Set the initial date to today when the screen is mounted + context.read().updateSelectedDate(DateTime.now()); + } + }); + } + @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + return Scaffold( - backgroundColor: Theme.of(context).colorScheme.surface, - body: SafeArea( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding( - padding: EdgeInsets.all(20.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - DateSelector(), - AddConductButton(), - ], + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + isDarkMode ? const Color.fromARGB(255, 35, 40, 55) : Colors.white, + isDarkMode ? const Color.fromARGB(255, 25, 30, 45) : Colors.white, + ], + ), + ), + child: SafeArea( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Conduct Tracker', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 24.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 4.h), + Text( + 'Track and manage conducts', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 14.sp, + letterSpacing: 0.5, + ), + ), + ], + ), + Row( + children: [ + Container( + padding: EdgeInsets.all(8.sp), + decoration: BoxDecoration( + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.15), + blurRadius: 16.r, + offset: Offset(0, 6.h), + spreadRadius: isDarkMode ? 1.r : 2.r, + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(12.r), + onTap: () { + context.read().toggleTheme(!isDarkMode); + }, + child: Icon( + isDarkMode ? Icons.light_mode : Icons.dark_mode, + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : theme.colorScheme.secondary, + size: 24.sp, + ), + ), + ), + ), + SizedBox(width: 8.w), + Container( + padding: EdgeInsets.all(8.sp), + decoration: BoxDecoration( + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.15), + blurRadius: 16.r, + offset: Offset(0, 6.h), + spreadRadius: isDarkMode ? 1.r : 2.r, + ), + ], + ), + child: Icon( + Icons.person_outline_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : theme.colorScheme.secondary, + size: 24.sp, + ), + ), + ], + ), + ], + ), + SizedBox(height: 24.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 240.w, + child: const DateSelector(), + ), + const AddConductButton(), + ], + ), + ], + ), ), - ), - const ConductCalendar(), - const SizedBox(height: 30), - StreamBuilder>( - stream: context.watch().conducts, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); - } + const ConductCalendar(), + SizedBox(height: 24.h), + StreamBuilder>( + stream: context.watch().conducts, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text( + 'Error: ${snapshot.error}', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 14.sp, + ), + ), + ); + } - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator()); - } + if (!snapshot.hasData) { + return Center( + child: Padding( + padding: EdgeInsets.all(24.w), + child: CircularProgressIndicator( + color: theme.colorScheme.secondary, + ), + ), + ); + } - final conducts = snapshot.data!; - final selectedDate = context.watch().selectedDate; - final todayConducts = context.read() - .filterConductsByDate(conducts, selectedDate); + final conducts = snapshot.data!; + final selectedDate = context.watch().selectedDate; + final todayConducts = context.read() + .filterConductsByDate(conducts, selectedDate); - if (todayConducts.isEmpty) { - return const NoConductsWidget(); - } + if (todayConducts.isEmpty) { + return const NoConductsWidget(); + } - return Column( - children: [ - ConductBarGraph( - conducts: todayConducts, - participationStrength: context - .read() - .getParticipationStrength(todayConducts), - ), - const SizedBox(height: 20), - ConductList(conducts: todayConducts), - ], - ); - }, - ), - ], + return Column( + children: [ + Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + decoration: BoxDecoration( + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], + ), + child: ConductBarGraph( + conducts: todayConducts, + participationStrength: context + .read() + .getParticipationStrength(todayConducts), + ), + ), + SizedBox(height: 16.h), + ConductList(conducts: todayConducts), + ], + ); + }, + ), + SizedBox(height: 24.h), + ], + ), ), ), ), diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/add_conduct_button.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/add_conduct_button.dart index acc1ee1d..ab7efaa2 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/add_conduct_button.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/add_conduct_button.dart @@ -11,6 +11,8 @@ class AddConductButton extends StatelessWidget { final isDarkMode = theme.brightness == Brightness.dark; return Container( + height: 56.h, + width: 56.h, decoration: BoxDecoration( gradient: LinearGradient( colors: [ @@ -28,21 +30,24 @@ class AddConductButton extends StatelessWidget { ), ], ), - child: FloatingActionButton( - backgroundColor: Colors.transparent, - elevation: 0, - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AddConductScreen(), - ), - ); - }, - child: Icon( - Icons.add_rounded, - color: Colors.white, - size: 24.sp, + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(16.r), + child: InkWell( + borderRadius: BorderRadius.circular(16.r), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AddConductScreen(), + ), + ); + }, + child: Icon( + Icons.add_rounded, + color: Colors.white, + size: 28.sp, + ), ), ), ); diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_bar_graph.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_bar_graph.dart index 620b9a87..ba75cf82 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_bar_graph.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_bar_graph.dart @@ -1,3 +1,4 @@ +import 'dart:math'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; @@ -5,7 +6,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../domain/entities/conduct.dart'; -class ConductBarGraph extends StatelessWidget { +class ConductBarGraph extends StatefulWidget { final List conducts; final List participationStrength; @@ -15,109 +16,324 @@ class ConductBarGraph extends StatelessWidget { required this.participationStrength, }); + @override + State createState() => _ConductBarGraphState(); +} + +class _ConductBarGraphState extends State with SingleTickerProviderStateMixin { + final ScrollController _scrollController = ScrollController(); + static const int visibleBars = 3; // Show exactly 3 bars at a time + bool showScrollIndicator = false; + late AnimationController _peekController; + + @override + void initState() { + super.initState(); + _peekController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1200), + ); + + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + showScrollIndicator = widget.conducts.length > visibleBars; + }); + if (showScrollIndicator) { + _startPeekAnimation(); + } + }); + } + + @override + void dispose() { + _scrollController.dispose(); + _peekController.dispose(); + super.dispose(); + } + + void _startPeekAnimation() { + Future.delayed(const Duration(milliseconds: 500), () { + if (!mounted) return; + + final maxScroll = _scrollController.position.maxScrollExtent; + _peekController.addListener(() { + if (!mounted) return; + + // Create a peek effect using a sine wave + final peekOffset = (maxScroll * 0.2) * // Peek 20% of the total scroll + (sin(_peekController.value * 2 * pi) * 0.5 + 0.5); // Smooth sine wave + + _scrollController.jumpTo(peekOffset); + }); + + _peekController.forward().then((_) { + if (!mounted) return; + _peekController.reset(); + // Smoothly scroll back to start + _scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + }); + }); + } + + @override + void didUpdateWidget(ConductBarGraph oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.conducts.length != oldWidget.conducts.length) { + setState(() { + showScrollIndicator = widget.conducts.length > visibleBars; + }); + if (showScrollIndicator && !_peekController.isAnimating) { + _startPeekAnimation(); + } + } + } + double _calculateMaxY() { - if (participationStrength.isEmpty) return 5.0; // Default max when no data - double maxParticipants = participationStrength.reduce((a, b) => a > b ? a : b); - // Add 2 to the max value to ensure bars don't reach the top + if (widget.participationStrength.isEmpty) return 5.0; + double maxParticipants = widget.participationStrength.reduce((a, b) => a > b ? a : b); return maxParticipants + 2.0; } + double _calculateBarWidth(BuildContext context) { + final availableWidth = MediaQuery.of(context).size.width - 32.w - 48.w; + return (availableWidth / 3) - 16.w; + } + @override Widget build(BuildContext context) { - return AspectRatio( - aspectRatio: 1, - child: Container( - padding: EdgeInsets.all(16.0.sp), - child: BarChart( - BarChartData( - alignment: BarChartAlignment.spaceEvenly, - maxY: _calculateMaxY(), - minY: 0, - gridData: const FlGridData(show: false), - borderData: FlBorderData( - show: true, - border: Border.all(color: Theme.of(context).colorScheme.tertiary), + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + final barWidth = _calculateBarWidth(context); + + return Container( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Participation Overview', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 18.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, ), - barTouchData: BarTouchData( - enabled: false, - touchTooltipData: BarTouchTooltipData( - fitInsideHorizontally: true, - fitInsideVertically: true, - direction: TooltipDirection.top, - tooltipMargin: 4, - getTooltipItem: (group, groupIndex, rod, rodIndex) { - return BarTooltipItem( - rod.toY.round().toString(), - GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: 16.sp, - ), - ); - }, - ), + ), + SizedBox(height: 4.h), + Text( + 'Strength distribution across conducts', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 14.sp, + letterSpacing: 0.5, ), - titlesData: FlTitlesData( - show: true, - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: (value, meta) => _buildTitle(value, meta, context), - reservedSize: 30.sp, + ), + if (showScrollIndicator) ...[ + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.swipe_rounded, + color: isDarkMode ? Colors.white70 : Colors.black54, + size: 16.sp, + ), + SizedBox(width: 4.w), + Text( + 'Scroll to see more', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + ), + ), + ], + ), + ), + ], + ), + ], + SizedBox(height: 16.h), + SingleChildScrollView( + controller: _scrollController, + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: SizedBox( + width: max( + MediaQuery.of(context).size.width - 32.w, + (barWidth + 16.w) * widget.conducts.length + 16.w, + ), + child: AspectRatio( + aspectRatio: 1.2, + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceEvenly, + maxY: _calculateMaxY(), + minY: 0, + gridData: FlGridData( + show: true, + drawVerticalLine: false, + horizontalInterval: 1, + getDrawingHorizontalLine: (value) => FlLine( + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.black.withOpacity(0.1), + strokeWidth: 1, + ), + ), + borderData: FlBorderData(show: false), + barTouchData: BarTouchData( + enabled: true, + touchTooltipData: BarTouchTooltipData( + fitInsideHorizontally: true, + fitInsideVertically: true, + direction: TooltipDirection.top, + tooltipMargin: 4, + tooltipPadding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 8.h, + ), + tooltipBorder: BorderSide( + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.black.withOpacity(0.1), + ), + tooltipRoundedRadius: 8.r, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + return BarTooltipItem( + '${rod.toY.round()} participants', + GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) => _buildTitle(value, meta, context), + reservedSize: 40.sp, + ), + ), + topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + if (value == value.roundToDouble()) { + return SideTitleWidget( + axisSide: meta.axisSide, + child: Text( + value.toInt().toString(), + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ), + ); + } + return const SizedBox.shrink(); + }, + reservedSize: 24.w, + ), + ), + rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), + ), + barGroups: _createBarGroups(context, barWidth), + ), + swapAnimationDuration: const Duration(milliseconds: 300), + swapAnimationCurve: Curves.easeInOut, ), ), - topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), - leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), - rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), ), - barGroups: _createBarGroups(), ), - ), + ], ), ); } Widget _buildTitle(double value, TitleMeta meta, BuildContext context) { - if (value.toInt() >= conducts.length) return const SizedBox.shrink(); + if (value.toInt() >= widget.conducts.length) return const SizedBox.shrink(); + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; return SideTitleWidget( axisSide: meta.axisSide, - space: 2, - fitInside: const SideTitleFitInsideData( - enabled: true, - axisPosition: 20, - parentAxisSize: 50, - distanceFromEdge: -10, - ), + space: 4.h, child: SizedBox( + width: 60.w, child: AutoSizeText( - conducts[value.toInt()].conductName, - style: GoogleFonts.poppins(fontWeight: FontWeight.w500), - maxLines: 1, - wrapWords: false, - overflow: TextOverflow.clip, + widget.conducts[value.toInt()].conductName, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black87, + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + maxLines: 2, + textAlign: TextAlign.center, + minFontSize: 8, ), ), ); } - List _createBarGroups() { + List _createBarGroups(BuildContext context, double barWidth) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + return List.generate( - conducts.length, + widget.conducts.length, (index) => BarChartGroupData( x: index, barRods: [ BarChartRodData( - toY: participationStrength[index], - gradient: const LinearGradient( + toY: widget.participationStrength[index], + gradient: LinearGradient( colors: [ - Color.fromARGB(255, 72, 30, 229), - Color.fromARGB(255, 130, 60, 229), + theme.colorScheme.secondary, + theme.colorScheme.secondary.withOpacity(0.8), ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, + begin: Alignment.topCenter, + end: Alignment.bottomCenter, ), - width: (200 * (1 / participationStrength.length)).w, + width: barWidth, borderRadius: BorderRadius.circular(4.r), + backDrawRodData: BackgroundBarChartRodData( + show: true, + toY: _calculateMaxY(), + color: isDarkMode + ? Colors.white.withOpacity(0.05) + : Colors.black.withOpacity(0.05), + ), ), ], showingTooltipIndicators: [0], diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_calendar.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_calendar.dart index 697c2f74..0ad0a2c4 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_calendar.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_calendar.dart @@ -1,88 +1,182 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; import '../providers/conduct_provider.dart'; import 'package:intl/intl.dart'; -class ConductCalendar extends StatelessWidget { +class ConductCalendar extends StatefulWidget { const ConductCalendar({super.key}); + @override + State createState() => _ConductCalendarState(); +} + +class _ConductCalendarState extends State { + final ScrollController _scrollController = ScrollController(); + final double dayWidth = 80.w; // Fixed width for each day item + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToSelectedDate(); + }); + } + + @override + void didUpdateWidget(ConductCalendar oldWidget) { + super.didUpdateWidget(oldWidget); + final previousDate = context.read().selectedDate; + final currentDate = context.watch().selectedDate; + + if (previousDate != currentDate) { + _scrollToSelectedDate(); + } + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _scrollToSelectedDate() { + if (!mounted) return; + + final selectedDate = context.read().selectedDate; + final today = DateTime.now(); + final startOfWeek = today.subtract(Duration(days: today.weekday - 1)); + + // Calculate the index of the selected date relative to the start of the week + final daysDifference = selectedDate.difference(startOfWeek).inDays; + + // Calculate scroll position + final scrollPosition = daysDifference * (dayWidth + 12.w); // dayWidth + spacing + + // Animate to the position + _scrollController.animateTo( + scrollPosition, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; final selectedDate = context.watch().selectedDate; - final currentDate = DateTime.now(); - - return SizedBox( - height: 110, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: 14, // Show 2 weeks of dates - itemBuilder: (context, index) { - final date = currentDate.subtract(Duration(days: 7 - index)); - final isSelected = _isSameDay(date, selectedDate); - - return GestureDetector( - onTap: () { - context.read().updateSelectedDate(date); - }, - child: Container( - width: 80, - margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), - decoration: BoxDecoration( - color: isSelected - ? Theme.of(context).colorScheme.primary - : Colors.transparent, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: isSelected - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, - ), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - DateFormat('EEE').format(date), - style: TextStyle( - fontSize: 14, - color: isSelected - ? Colors.white - : Theme.of(context).colorScheme.onSurface, - ), - ), - const SizedBox(height: 4), - Text( - DateFormat('d').format(date), - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: isSelected - ? Colors.white - : Theme.of(context).colorScheme.onSurface, - ), + + // Generate dates for the week + final today = DateTime.now(); + final startOfWeek = today.subtract(Duration(days: today.weekday - 1)); + final dates = List.generate(7, (index) => startOfWeek.add(Duration(days: index))); + + // Add listener for date changes + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _scrollToSelectedDate(); + } + }); + + return SingleChildScrollView( + controller: _scrollController, + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: Row( + children: [ + SizedBox(width: 16.w), // Initial padding + ...dates.map((date) { + final isSelected = DateFormat('d MMM yyyy').format(date) == + DateFormat('d MMM yyyy').format(selectedDate); + return Padding( + padding: EdgeInsets.only(right: 12.w), + child: GestureDetector( + onTap: () { + context.read().updateSelectedDate(date); + }, + child: Container( + width: dayWidth, + padding: EdgeInsets.symmetric(vertical: 12.h), + decoration: BoxDecoration( + gradient: isSelected + ? LinearGradient( + colors: [ + theme.colorScheme.secondary, + theme.colorScheme.secondary.withOpacity(0.8), + ], + ) + : null, + color: isSelected + ? null + : isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: isSelected + ? theme.colorScheme.secondary.withOpacity(0.3) + : isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], ), - const SizedBox(height: 4), - Text( - DateFormat('MMM').format(date), - style: TextStyle( - fontSize: 14, - color: isSelected - ? Colors.white - : Theme.of(context).colorScheme.onSurface, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + DateFormat('E').format(date), + style: GoogleFonts.poppins( + color: isSelected + ? Colors.white + : isDarkMode + ? Colors.white70 + : Colors.black54, + fontSize: 14.sp, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 4.h), + Text( + date.day.toString(), + style: GoogleFonts.poppins( + color: isSelected + ? Colors.white + : isDarkMode + ? Colors.white + : Colors.black87, + fontSize: 18.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 4.h), + Text( + DateFormat('MMM').format(date), + style: GoogleFonts.poppins( + color: isSelected + ? Colors.white + : isDarkMode + ? Colors.white70 + : Colors.black54, + fontSize: 12.sp, + letterSpacing: 0.5, + ), + ), + ], ), - ], + ), ), - ), - ); - }, + ); + }).toList(), + SizedBox(width: 4.w), // Final padding + ], ), ); } - - bool _isSameDay(DateTime date1, DateTime date2) { - return date1.year == date2.year && - date1.month == date2.month && - date1.day == date2.day; - } -} \ No newline at end of file +} diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_list.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_list.dart index f51b03ae..537e1c3c 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_list.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_list.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; import '../../domain/entities/conduct.dart'; -import 'conduct_tile.dart'; import '../pages/conduct_details_screen.dart'; class ConductList extends StatelessWidget { @@ -13,28 +14,163 @@ class ConductList extends StatelessWidget { @override Widget build(BuildContext context) { - return ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: conducts.length, - itemBuilder: (context, index) { - final conduct = conducts[index]; - return InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ConductDetailsScreen(conductId: conduct.id), + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Today\'s Conducts', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 18.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 4.h), + Text( + 'Tap on a conduct to view details', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 14.sp, + letterSpacing: 0.5, + ), ), - ); - }, - child: ConductTile( - conductNumber: index, - conductName: conduct.conductName, - conductType: conduct.conductType, + ], + ), + ), + SizedBox(height: 16.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: conducts.length, + itemBuilder: (context, index) { + final conduct = conducts[index]; + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Container( + decoration: BoxDecoration( + color: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(16.r), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ConductDetailsScreen(conductId: conduct.id), + ), + ); + }, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Row( + children: [ + Container( + width: 48.w, + height: 48.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + theme.colorScheme.secondary, + theme.colorScheme.secondary.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: theme.colorScheme.secondary.withOpacity(0.3), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], + ), + child: Center( + child: Icon( + Conduct.getIconForConductType(conduct.conductType), + color: Colors.white, + size: 24.sp, + ), + ), + ), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + conduct.conductType, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + letterSpacing: 0.5, + ), + ), + Text( + conduct.conductName, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + ], + ), + ), + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.black.withOpacity(0.05), + borderRadius: BorderRadius.circular(8.r), + ), + child: Center( + child: Icon( + Icons.arrow_forward_ios_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.7) + : Colors.black54, + size: 16.sp, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + }, ), - ); - }, + ), + ], ); } } \ No newline at end of file diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_tile.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_tile.dart index 7edd50c3..6fcfff41 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_tile.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/conduct_tile.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; class ConductTile extends StatelessWidget { final String conductType; @@ -14,56 +16,117 @@ class ConductTile extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + return Container( - padding: const EdgeInsets.all(16.0), - color: Colors.transparent, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(right: 30.0), - child: Container( - height: 70, - width: 70, + margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + isDarkMode + ? const Color.fromARGB(255, 40, 45, 60) + : Colors.white, + ], + ), + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + height: 60.w, + width: 60.w, decoration: BoxDecoration( - color: const Color.fromARGB(255, 53, 14, 145), - borderRadius: BorderRadius.circular(10), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + theme.colorScheme.secondary, + theme.colorScheme.secondary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(10.r), + boxShadow: [ + BoxShadow( + color: theme.colorScheme.secondary.withOpacity(0.3), + blurRadius: 8.r, + offset: Offset(0, 4.h), + ), + ], ), child: Center( child: Text( (conductNumber + 1).toString(), - style: const TextStyle( + style: GoogleFonts.poppins( color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 24, + fontWeight: FontWeight.w600, + fontSize: 20.sp, ), ), ), ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - conductType, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + conductType, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 12.sp, + fontWeight: FontWeight.w500, + letterSpacing: 0.5, + ), ), - ), - Text( - conductName, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, + SizedBox(height: 4.h), + Text( + conductName, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), ), - ), - ], + ], + ), ), - ), - const Icon(Icons.arrow_forward_outlined), - ], + Container( + padding: EdgeInsets.all(8.w), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : theme.colorScheme.secondary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.arrow_forward_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.7) + : theme.colorScheme.secondary, + size: 20.sp, + ), + ), + ], + ), ), ); } diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/date_selector.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/date_selector.dart index d0e86b9d..bea4f75e 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/date_selector.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/date_selector.dart @@ -1,25 +1,121 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; import '../providers/conduct_provider.dart'; +import 'package:intl/intl.dart'; class DateSelector extends StatelessWidget { const DateSelector({super.key}); @override Widget build(BuildContext context) { - return TextButton( - onPressed: () async { - final DateTime? picked = await showDatePicker( - context: context, - initialDate: context.read().selectedDate, - firstDate: DateTime(2022), - lastDate: DateTime(2025), - ); - if (picked != null) { - context.read().updateSelectedDate(picked); - } - }, - child: const Text('Select Date'), + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + final selectedDate = context.watch().selectedDate; + + return Container( + height: 56.h, + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + isDarkMode + ? const Color.fromARGB(255, 40, 45, 60) + : Colors.white, + ], + ), + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 16.r, + offset: Offset(0, 6.h), + spreadRadius: 2.r, + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(16.r), + onTap: () async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: selectedDate, + firstDate: DateTime(2022), + lastDate: DateTime(2025), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: theme.colorScheme.copyWith( + primary: theme.colorScheme.secondary, + onPrimary: Colors.white, + surface: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + onSurface: isDarkMode ? Colors.white : Colors.black87, + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: theme.colorScheme.secondary, + ), + ), + dialogBackgroundColor: isDarkMode + ? const Color.fromARGB(255, 35, 40, 55) + : Colors.white, + ), + child: child!, + ); + }, + ); + if (picked != null && context.mounted) { + context.read().updateSelectedDate(picked); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.calendar_today_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : theme.colorScheme.secondary, + size: 24.sp, + ), + SizedBox(width: 12.w), + Text( + DateFormat('d MMM yyyy').format(selectedDate), + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + ], + ), + Icon( + Icons.arrow_drop_down_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.7) + : theme.colorScheme.secondary.withOpacity(0.7), + size: 24.sp, + ), + ], + ), + ), + ), ); } } \ No newline at end of file diff --git a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/no_conducts_widget.dart b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/no_conducts_widget.dart index 95e19007..9c9be57e 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/no_conducts_widget.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/presentation/widgets/no_conducts_widget.dart @@ -1,16 +1,76 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:google_fonts/google_fonts.dart'; class NoConductsWidget extends StatelessWidget { const NoConductsWidget({super.key}); @override Widget build(BuildContext context) { - return const Center( - child: Padding( - padding: EdgeInsets.all(16.0), - child: Text( - 'No conducts scheduled for this date', - style: TextStyle(fontSize: 16), + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + + return Center( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 24.w, vertical: 32.h), + padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 40.h), + width: double.infinity, + decoration: BoxDecoration( + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + borderRadius: BorderRadius.circular(24.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.1), + blurRadius: 16.r, + offset: Offset(0, 8.h), + spreadRadius: 2.r, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.all(24.w), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.grey[100], + borderRadius: BorderRadius.circular(20.r), + ), + child: Icon( + Icons.event_busy_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.7) + : theme.colorScheme.secondary, + size: 48.sp, + ), + ), + SizedBox(height: 24.h), + Text( + 'No Conducts Scheduled', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 24.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 8.h), + Text( + 'There are no conducts scheduled for this date', + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 16.sp, + letterSpacing: 0.5, + ), + ), + ], ), ), ); diff --git a/trooptrak_final_application/lib/features/nominal_roll/presentation/pages/nominal_roll_screen.dart b/trooptrak_final_application/lib/features/nominal_roll/presentation/pages/nominal_roll_screen.dart index 767c58a1..92fdc06d 100644 --- a/trooptrak_final_application/lib/features/nominal_roll/presentation/pages/nominal_roll_screen.dart +++ b/trooptrak_final_application/lib/features/nominal_roll/presentation/pages/nominal_roll_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'dart:async'; +import 'package:google_fonts/google_fonts.dart'; // Domain import '../../domain/entities/user.dart'; @@ -127,24 +128,99 @@ class _NominalRollPageState extends State { Widget _buildHeader(BuildContext context, bool isDarkMode, ThemeData theme, ThemeManager themeManager) { return Padding( - padding: EdgeInsets.only(top: 8.h, left: 24.w, right: 24.w), + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Nominal Roll', - style: theme.textTheme.displayLarge?.copyWith( - fontWeight: FontWeight.w600, - fontSize: 24.sp, - letterSpacing: 0.5, - color: isDarkMode ? Colors.white : Colors.black87, - ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Nominal Roll', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 24.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + SizedBox(height: 4.h), + Text( + 'Our Family of Soldiers', + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 14.sp, + letterSpacing: 0.5, + ), + ), + ], ), Row( children: [ - _buildThemeToggle(isDarkMode, theme, themeManager), - SizedBox(width: 12.w), - _buildUserIcon(isDarkMode), + Container( + padding: EdgeInsets.all(8.sp), + decoration: BoxDecoration( + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.15), + blurRadius: 16.r, + offset: Offset(0, 6.h), + spreadRadius: isDarkMode ? 1.r : 2.r, + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(12.r), + onTap: () { + themeManager.toggleTheme(themeManager.themeMode == ThemeMode.light); + }, + child: Icon( + themeManager.themeMode == ThemeMode.dark + ? Icons.light_mode_rounded + : Icons.dark_mode_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : theme.colorScheme.secondary, + size: 24.sp, + ), + ), + ), + ), + SizedBox(width: 8.w), + Container( + padding: EdgeInsets.all(8.sp), + decoration: BoxDecoration( + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.15), + blurRadius: 16.r, + offset: Offset(0, 6.h), + spreadRadius: isDarkMode ? 1.r : 2.r, + ), + ], + ), + child: Icon( + Icons.person_outline_rounded, + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : theme.colorScheme.secondary, + size: 24.sp, + ), + ), ], ), ], @@ -152,63 +228,6 @@ class _NominalRollPageState extends State { ); } - Widget _buildThemeToggle(bool isDarkMode, ThemeData theme, ThemeManager themeManager) { - return Container( - padding: EdgeInsets.all(8.sp), - decoration: BoxDecoration( - color: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, - borderRadius: BorderRadius.circular(12.r), - boxShadow: [ - BoxShadow( - color: isDarkMode - ? Colors.black.withOpacity(0.3) - : Colors.black.withOpacity(0.15), - blurRadius: 16.r, - offset: Offset(0, 6.h), - spreadRadius: isDarkMode ? 1.r : 2.r, - ), - ], - ), - child: GestureDetector( - onTap: () { - themeManager.toggleTheme(themeManager.themeMode == ThemeMode.light); - }, - child: Icon( - themeManager.themeMode == ThemeMode.dark - ? Icons.light_mode_rounded - : Icons.dark_mode_rounded, - color: isDarkMode ? Colors.white70 : Colors.black54, - size: 20.sp, - ), - ), - ); - } - - Widget _buildUserIcon(bool isDarkMode) { - return Container( - padding: EdgeInsets.all(8.sp), - decoration: BoxDecoration( - color: isDarkMode ? const Color.fromARGB(255, 45, 50, 65) : Colors.white, - borderRadius: BorderRadius.circular(12.r), - boxShadow: [ - BoxShadow( - color: isDarkMode - ? Colors.black.withOpacity(0.3) - : Colors.black.withOpacity(0.15), - blurRadius: 16.r, - offset: Offset(0, 6.h), - spreadRadius: isDarkMode ? 1.r : 2.r, - ), - ], - ), - child: Image.asset( - 'lib/assets/user.png', - width: 20.w, - height: 20.h, - ), - ); - } - Widget _buildUserGrid(BuildContext context) { return StreamBuilder>( stream: context.read().users, @@ -320,8 +339,6 @@ class _NominalRollPageState extends State { children: [ _buildHeader(context, isDarkMode, theme, themeManager), SizedBox(height: 20.h), - _buildSubtitle(context, isDarkMode, theme), - SizedBox(height: 12.h), _buildSearchBar(context, isDarkMode, theme), SizedBox(height: 20.h), Expanded( @@ -334,20 +351,6 @@ class _NominalRollPageState extends State { ); } - Widget _buildSubtitle(BuildContext context, bool isDarkMode, ThemeData theme) { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 24.w), - child: Text( - 'Our Family of Soldiers:', - style: theme.textTheme.displayMedium?.copyWith( - fontSize: 16.sp, - fontWeight: FontWeight.w500, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), - ), - ); - } - Widget _buildQRScannerButton(BuildContext context, ThemeData theme, bool isDarkMode) { return Container( decoration: BoxDecoration( From 6111fe2eb9245127b017be75384d9210c3a44bda Mon Sep 17 00:00:00 2001 From: Aakash S/O Rengarajan Ramaswamy Date: Thu, 26 Dec 2024 11:20:44 +0800 Subject: [PATCH 3/3] Add autocomplete and non-strict dropdown for excuse status selection for quick use --- .../usecases/filter_participants_usecase.dart | 4 +- .../pages/add_update_status_page.dart | 165 +++++++++++++++--- 2 files changed, 146 insertions(+), 23 deletions(-) diff --git a/trooptrak_final_application/lib/features/conduct_tracker/domain/usecases/filter_participants_usecase.dart b/trooptrak_final_application/lib/features/conduct_tracker/domain/usecases/filter_participants_usecase.dart index 14d2f661..008a3ed6 100644 --- a/trooptrak_final_application/lib/features/conduct_tracker/domain/usecases/filter_participants_usecase.dart +++ b/trooptrak_final_application/lib/features/conduct_tracker/domain/usecases/filter_participants_usecase.dart @@ -2,7 +2,7 @@ class FilterParticipantsUseCase { final Map> conductTypeRestrictions = { 'run': ['Ex RMJ', 'Ex Lower Limb', 'LD'], 'route march': [ - 'Ex RMJ', 'Ex Heavy Loads', 'Ex Lower Limbs', 'LD', + 'Ex RMJ', 'Ex Heavy Loads', 'Ex Lower Limb', 'LD', 'Ex Uniform', 'Ex Boots', 'Ex FLEGs', ], 'ippt': [ @@ -10,7 +10,7 @@ class FilterParticipantsUseCase { 'Ex Pushup', 'Ex Situp' ], 'outfield': [ - 'Ex Sunlight', 'Ex grass', 'Ex Outfield', + 'Ex Sunlight', 'Ex Grass', 'Ex Outfield', 'Ex Uniform', 'Ex Boots' ], 'metabolic circuit': [ diff --git a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/add_update_status_page.dart b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/add_update_status_page.dart index 431badcb..e0986ade 100644 --- a/trooptrak_final_application/lib/features/detailed_view/presentation/pages/add_update_status_page.dart +++ b/trooptrak_final_application/lib/features/detailed_view/presentation/pages/add_update_status_page.dart @@ -27,6 +27,9 @@ class _AddUpdateStatusPageState extends State { late TextEditingController _statusNameController; late DateTime _startDateTime; late DateTime _endDateTime; + final FocusNode _statusNameFocusNode = FocusNode(); + OverlayEntry? _overlayEntry; + final LayerLink _layerLink = LayerLink(); final List _statusTypes = [ "Select status type...", @@ -35,6 +38,22 @@ class _AddUpdateStatusPageState extends State { "Medical Appointment", ]; + final List _excuseTypes = [ + 'Ex RMJ', + 'Ex Lower Limb', + 'Ex Heavy Loads', + 'Ex Uniform', + 'Ex Boots', + 'Ex FLEGs', + 'Ex Upper Limb', + 'Ex Pushup', + 'Ex Situp', + 'Ex Sunlight', + 'Ex Grass', + 'Ex Outfield', + 'LD', + ]; + @override void initState() { super.initState(); @@ -46,14 +65,108 @@ class _AddUpdateStatusPageState extends State { _endDateTime = widget.status != null ? DateTime.parse(widget.status!.endId) : DateTime.now().add(const Duration(hours: 1)); + + _statusNameController.addListener(() { + if (_statusType == 'Excuse') { + _updateOverlay(); + } + }); + + _statusNameFocusNode.addListener(() { + if (!_statusNameFocusNode.hasFocus) { + _removeOverlay(); + } else if (_statusType == 'Excuse') { + _updateOverlay(); + } + }); } @override void dispose() { _statusNameController.dispose(); + _statusNameFocusNode.dispose(); + _removeOverlay(); super.dispose(); } + void _updateOverlay() { + _removeOverlay(); + if (_statusNameFocusNode.hasFocus && _statusType == 'Excuse') { + _overlayEntry = _createOverlayEntry(); + Overlay.of(context).insert(_overlayEntry!); + } + } + + void _removeOverlay() { + _overlayEntry?.remove(); + _overlayEntry = null; + } + + OverlayEntry _createOverlayEntry() { + final theme = Theme.of(context); + final isDarkMode = theme.brightness == Brightness.dark; + final renderBox = context.findRenderObject() as RenderBox; + final size = renderBox.size; + + final filteredExcuses = _excuseTypes + .where((excuse) => excuse + .toLowerCase() + .contains(_statusNameController.text.toLowerCase())) + .toList(); + + return OverlayEntry( + builder: (context) => Positioned( + width: size.width - 32.w, + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: Offset(0, 70.h), + child: Material( + elevation: 8, + borderRadius: BorderRadius.circular(12.r), + color: isDarkMode + ? const Color.fromARGB(255, 45, 50, 65) + : Colors.white, + child: Container( + constraints: BoxConstraints(maxHeight: 200.h), + child: ListView.builder( + padding: EdgeInsets.symmetric(vertical: 8.h), + shrinkWrap: true, + itemCount: filteredExcuses.length, + itemBuilder: (context, index) { + final excuse = filteredExcuses[index]; + return InkWell( + onTap: () { + _statusNameController.text = excuse; + _statusNameController.selection = TextSelection.fromPosition( + TextPosition(offset: excuse.length), + ); + _removeOverlay(); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 12.h, + ), + child: Text( + excuse, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + }, + ), + ), + ), + ), + ), + ); + } + Future _selectDateTime(bool isStart) async { final date = await showDatePicker( context: context, @@ -270,30 +383,40 @@ class _AddUpdateStatusPageState extends State { ), ], ), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.w), - child: TextFormField( - controller: _statusNameController, - style: GoogleFonts.poppins( - color: isDarkMode ? Colors.white : theme.colorScheme.onSurface, - fontSize: 16.sp, - fontWeight: FontWeight.w500, - ), - decoration: InputDecoration( - labelText: 'Status Name', - labelStyle: GoogleFonts.poppins( - color: theme.colorScheme.tertiary.withOpacity(0.7), - fontSize: 14.sp, + child: CompositedTransformTarget( + link: _layerLink, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: TextFormField( + controller: _statusNameController, + focusNode: _statusNameFocusNode, + style: GoogleFonts.poppins( + color: isDarkMode ? Colors.white : theme.colorScheme.onSurface, + fontSize: 16.sp, fontWeight: FontWeight.w500, ), - border: InputBorder.none, + decoration: InputDecoration( + labelText: 'Status Name', + labelStyle: GoogleFonts.poppins( + color: theme.colorScheme.tertiary.withOpacity(0.7), + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + border: InputBorder.none, + suffixIcon: _statusType == 'Excuse' + ? Icon( + Icons.arrow_drop_down, + color: isDarkMode ? Colors.white70 : Colors.black54, + ) + : null, + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a status name'; + } + return null; + }, ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a status name'; - } - return null; - }, ), ), ),