diff --git a/frontend/lib/ui/screens/dashboard/dashboard_screen.dart b/frontend/lib/ui/screens/dashboard/dashboard_screen.dart index f9fd745..a442be8 100644 --- a/frontend/lib/ui/screens/dashboard/dashboard_screen.dart +++ b/frontend/lib/ui/screens/dashboard/dashboard_screen.dart @@ -677,6 +677,8 @@ class _DashboardScreenState extends ConsumerState { } return LayoutBuilder(builder: (context, constraints) { + final isWindowsApp = Theme.of(context).platform == TargetPlatform.windows || constraints.maxWidth >= 1000; + int crossAxisCount; if (constraints.maxWidth >= 1000) { crossAxisCount = isAdmin ? 4 : 3; @@ -685,6 +687,9 @@ class _DashboardScreenState extends ConsumerState { } else { crossAxisCount = 1; } + + final isSquare = isWindowsApp && crossAxisCount > 1; + return GridView( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -692,13 +697,14 @@ class _DashboardScreenState extends ConsumerState { crossAxisCount: crossAxisCount, crossAxisSpacing: 16, mainAxisSpacing: 16, - mainAxisExtent: crossAxisCount == 1 ? 110 : 130, + mainAxisExtent: isSquare ? 115 : (crossAxisCount == 1 ? 85 : 95), ), children: [ StatCard( title: 'Total Students', value: stats.totalStudents.toString(), icon: Icons.school_rounded, + isSquare: isSquare, onTap: () { ref.read(studentQueryProvider.notifier).reset(); ref.invalidate(studentPageProvider); @@ -711,6 +717,7 @@ class _DashboardScreenState extends ConsumerState { value: stats.activeUsers.toString(), icon: Icons.group, iconColor: Colors.blue, + isSquare: isSquare, onTap: () { ref.invalidate(usersProvider); ref.read(activeTabProvider.notifier).setTab('Users'); @@ -721,6 +728,7 @@ class _DashboardScreenState extends ConsumerState { value: stats.completedDocuments.toString(), icon: Icons.fact_check_rounded, iconColor: AppColors.primaryGreen, + isSquare: isSquare, onTap: () { ref.invalidate(foldersProvider); ref.invalidate(documentPageProvider); @@ -732,6 +740,7 @@ class _DashboardScreenState extends ConsumerState { value: stats.missingDocuments.toString(), icon: Icons.assignment_late_rounded, iconColor: Colors.orange, + isSquare: isSquare, onTap: () { ref.invalidate(foldersProvider); ref.invalidate(documentPageProvider); diff --git a/frontend/lib/ui/shared/cards/stat_card.dart b/frontend/lib/ui/shared/cards/stat_card.dart index 912749b..e133ddf 100644 --- a/frontend/lib/ui/shared/cards/stat_card.dart +++ b/frontend/lib/ui/shared/cards/stat_card.dart @@ -7,6 +7,7 @@ class StatCard extends StatelessWidget { final IconData icon; final Color? iconColor; final VoidCallback? onTap; + final bool isSquare; const StatCard({ super.key, @@ -16,6 +17,7 @@ class StatCard extends StatelessWidget { required this.icon, this.iconColor, this.onTap, + this.isSquare = false, }); @override @@ -23,11 +25,10 @@ class StatCard extends StatelessWidget { return LayoutBuilder( builder: (context, constraints) { final isSmall = constraints.maxWidth < 220; - final iconSize = isSmall ? 34.0 : 36.0; - final titleFontSize = isSmall ? 12.0 : 14.0; - final valueFontSize = isSmall ? 22.0 : 28.0; - final padding = isSmall ? 12.0 : 20.0; - final iconPadding = isSmall ? 8.0 : 12.0; + final iconSize = isSmall ? 24.0 : 28.0; + final titleFontSize = isSmall ? 11.0 : 13.0; + final valueFontSize = isSmall ? 20.0 : 24.0; + final padding = isSmall ? 10.0 : 14.0; return Container( decoration: BoxDecoration( @@ -35,9 +36,9 @@ class StatCard extends StatelessWidget { borderRadius: BorderRadius.circular(12.0), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.03), - blurRadius: 10, - offset: const Offset(0, 4), + color: Colors.black.withValues(alpha: 0.03), + blurRadius: 8, + offset: const Offset(0, 3), ), ], ), @@ -49,73 +50,146 @@ class StatCard extends StatelessWidget { borderRadius: BorderRadius.circular(12.0), child: Padding( padding: EdgeInsets.all(padding), - child: Row( - children: [ - // Icon Container - Padding( - padding: EdgeInsets.all(iconPadding), - child: Icon( - icon, - size: iconSize, - color: iconColor ?? const Color(0xFF1C8248), - ), - ), - const SizedBox(width: 12), - // Text Details - Expanded( - child: Column( + child: isSquare + ? Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - title, - maxLines: 2, - overflow: TextOverflow.visible, - style: TextStyle( - fontSize: titleFontSize, - color: Colors.grey.shade600, - fontWeight: FontWeight.w500, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: (iconColor ?? const Color(0xFF1C8248)).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + icon, + size: iconSize, + color: iconColor ?? const Color(0xFF1C8248), + ), + ), + if (onTap != null) + Icon( + Icons.chevron_right_rounded, + color: Colors.grey.shade400, + size: 16, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Text( + value, + style: TextStyle( + fontSize: valueFontSize, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ), + const SizedBox(height: 2), + Text( + title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: titleFontSize, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + if (subtitle != null) ...[ + const SizedBox(height: 2), + Text( + subtitle!, + style: TextStyle( + fontSize: isSmall ? 9.0 : 11.0, + color: Colors.grey.shade500, + fontWeight: FontWeight.w500, + ), + ), + ], + ], + ), + ], + ) + : Row( + children: [ + // Icon Container + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: (iconColor ?? const Color(0xFF1C8248)).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + icon, + size: iconSize, + color: iconColor ?? const Color(0xFF1C8248), ), ), - const SizedBox(height: 4), - // FittedBox shrinks the text dynamically if the number gets too massive - FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: Text( - value, - style: TextStyle( - fontSize: valueFontSize, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), + const SizedBox(width: 12), + // Text Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + title, + maxLines: 2, + overflow: TextOverflow.visible, + style: TextStyle( + fontSize: titleFontSize, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 2), + FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Text( + value, + style: TextStyle( + fontSize: valueFontSize, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ), + if (subtitle != null) ...[ + const SizedBox(height: 2), + Text( + subtitle!, + style: TextStyle( + fontSize: isSmall ? 9.0 : 11.0, + color: Colors.grey.shade500, + fontWeight: FontWeight.w500, + ), + ), + ], + ], ), ), - if (subtitle != null) ...[ - const SizedBox(height: 2), - Text( - subtitle!, - style: TextStyle( - fontSize: isSmall ? 10.0 : 12.0, - color: Colors.grey.shade500, - fontWeight: FontWeight.w500, + if (onTap != null) + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Icon( + Icons.chevron_right_rounded, + color: Colors.grey.shade400, + size: 18, ), ), - ], ], ), - ), - if (onTap != null) - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Icon( - Icons.chevron_right_rounded, - color: Colors.grey.shade400, - size: 20, - ), - ), - ], - ), ), ), ), diff --git a/frontend/lib/ui/shared/inputs/app_search_bar.dart b/frontend/lib/ui/shared/inputs/app_search_bar.dart index 7d6b9bc..5e94f37 100644 --- a/frontend/lib/ui/shared/inputs/app_search_bar.dart +++ b/frontend/lib/ui/shared/inputs/app_search_bar.dart @@ -262,13 +262,8 @@ class _AppSearchBarState extends ConsumerState { onPressed: () { _controller.clear(); widget.onChanged?.call(''); - if (widget.collapsible) { - _focusNode.unfocus(); - _removeOverlay(); - setState(() => _isExpanded = false); - } else { - _focusNode.requestFocus(); - } + widget.onSubmitted?.call(''); + _focusNode.requestFocus(); }, ) : null, diff --git a/frontend/lib/ui/shared/widgets/abstract_background.dart b/frontend/lib/ui/shared/widgets/abstract_background.dart index d1e5472..18e5649 100644 --- a/frontend/lib/ui/shared/widgets/abstract_background.dart +++ b/frontend/lib/ui/shared/widgets/abstract_background.dart @@ -1,8 +1,7 @@ -import 'dart:math' as math; import 'package:flutter/material.dart'; import '../../../core/constants/app_colors.dart'; -/// A lightweight, static abstract background decorator featuring exactly 3 large, solid shapes (no outlines). +/// A lightweight, static abstract background decorator featuring a smooth, modern gradient background. /// Fully responsive across Mobile, Web, and Desktop platforms. class AbstractBackground extends StatelessWidget { final Widget child; @@ -14,87 +13,21 @@ class AbstractBackground extends StatelessWidget { @override Widget build(BuildContext context) { - return Stack( - fit: StackFit.expand, - children: [ - // Static abstract background shapes layer (Ignored for pointer events) - const IgnorePointer( - child: _AbstractShapesCanvas(), - ), - // Screen Content Layer - child, - ], - ); - } -} - -class _AbstractShapesCanvas extends StatelessWidget { - const _AbstractShapesCanvas(); - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (context, constraints) { - final width = constraints.maxWidth; - final height = constraints.maxHeight; - - if (width == 0 || height == 0) return const SizedBox.shrink(); - - return Stack( - children: [ - // Base background tint - Container(color: AppColors.pageBackground), - - // Shape 1 (Top-Right): Large Solid Circle - Positioned( - top: -height * 0.12, - right: -width * 0.10, - child: Container( - width: math.max(320, width * 0.55), - height: math.max(320, width * 0.55), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColors.primaryGreen.withValues(alpha: 0.075), - ), - ), - ), - - // Shape 2 (Top-Left): Large Solid Tilted Rect Block - Positioned( - top: height * 0.08, - left: -width * 0.12, - child: Transform.rotate( - angle: -math.pi / 6, - child: Container( - width: math.max(260, width * 0.45), - height: math.max(140, width * 0.22), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(48), - color: AppColors.darkGreen.withValues(alpha: 0.055), - ), - ), - ), - ), - - // Shape 3 (Bottom-Right): Large Solid Tilted Pill Capsule - Positioned( - bottom: height * 0.05, - right: -width * 0.06, - child: Transform.rotate( - angle: math.pi / 5, - child: Container( - width: math.max(280, width * 0.50), - height: math.max(120, width * 0.22), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(60), - color: AppColors.primaryGreen.withValues(alpha: 0.065), - ), - ), - ), - ), + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + const Color(0xFFEAF3ED), + AppColors.pageBackground, + const Color(0xFFE4F0E8), + AppColors.pageBackground, ], - ); - }, + stops: const [0.0, 0.35, 0.7, 1.0], + ), + ), + child: child, ); } }