From c78231d0fd9240e0c94d99be91f7196d865a9ba1 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Wed, 29 Oct 2025 14:30:51 +0530 Subject: [PATCH] feat: Add theme customization feature with ThemeEditorWidget - Introduced ThemeEditorWidget for user-friendly theme selection. - Added ThemeOption class to manage theme properties. - Implemented ThemeController to handle theme application logic. - Updated ThemeCustomizer to allow external theme changes. - Refactored wave background components to support dynamic colors. - Updated various screens to utilize the new theme system. - Enhanced UI elements with consistent styling and improved responsiveness. --- lib/helpers/theme/admin_theme.dart | 283 +++++++------- lib/helpers/theme/theme_customizer.dart | 7 +- lib/helpers/theme/theme_editor_widget.dart | 271 +++++++++++++ lib/helpers/widgets/wave_background.dart | 39 +- lib/view/auth/forgot_password_screen.dart | 4 +- lib/view/auth/login_option_screen.dart | 11 +- lib/view/auth/login_screen.dart | 2 +- lib/view/auth/mpin_auth_screen.dart | 19 +- lib/view/auth/otp_login_form.dart | 4 +- lib/view/auth/request_demo_bottom_sheet.dart | 376 ++++++++++--------- lib/view/directory/notes_view.dart | 6 +- lib/view/layouts/layout.dart | 9 +- lib/view/layouts/offline_screen.dart | 14 +- lib/view/layouts/user_profile_right_bar.dart | 192 +++++----- 14 files changed, 795 insertions(+), 442 deletions(-) create mode 100644 lib/helpers/theme/theme_editor_widget.dart diff --git a/lib/helpers/theme/admin_theme.dart b/lib/helpers/theme/admin_theme.dart index a0a4444..55d5680 100644 --- a/lib/helpers/theme/admin_theme.dart +++ b/lib/helpers/theme/admin_theme.dart @@ -2,39 +2,9 @@ import 'package:flutter/material.dart'; import 'package:marco/helpers/theme/theme_customizer.dart'; enum LeftBarThemeType { light, dark } - enum ContentThemeType { light, dark } - enum RightBarThemeType { light, dark } -enum ContentThemeColor { - primary, - secondary, - success, - info, - warning, - danger, - light, - dark, - pink, - green, - red, - brandRed, - brandGreen; - - Color get color { - return (AdminTheme.theme.contentTheme.getMappedIntoThemeColor[this] - ?['color']) ?? - Colors.black; - } - - Color get onColor { - return (AdminTheme.theme.contentTheme.getMappedIntoThemeColor[this] - ?['onColor']) ?? - Colors.white; - } -} - class LeftBarTheme { final Color background, onBackground; final Color labelColor; @@ -48,16 +18,15 @@ class LeftBarTheme { this.activeItemBackground = const Color(0x15663399), }); - //-------------------------------------- Left Bar Theme ----------------------------------------// - static final LeftBarTheme lightLeftBarTheme = LeftBarTheme(); static final LeftBarTheme darkLeftBarTheme = LeftBarTheme( - background: const Color(0xff282c32), - onBackground: const Color(0xffdcdcdc), - labelColor: const Color(0xff32BFAE), - activeItemBackground: const Color(0x1532BFAE), - activeItemColor: const Color(0xff32BFAE)); + background: const Color(0xff282c32), + onBackground: const Color(0xffdcdcdc), + labelColor: const Color(0xff32BFAE), + activeItemBackground: const Color(0x1532BFAE), + activeItemColor: const Color(0xff32BFAE), + ); static LeftBarTheme getThemeFromType(LeftBarThemeType leftBarThemeType) { switch (leftBarThemeType) { @@ -78,13 +47,12 @@ class TopBarTheme { this.onBackground = const Color(0xff313a46), }); - //-------------------------------------- Left Bar Theme ----------------------------------------// - static final TopBarTheme lightTopBarTheme = TopBarTheme(); static final TopBarTheme darkTopBarTheme = TopBarTheme( - background: const Color(0xff2c3036), - onBackground: const Color(0xffdcdcdc)); + background: const Color(0xff2c3036), + onBackground: const Color(0xffdcdcdc), + ); } class RightBarTheme { @@ -98,19 +66,41 @@ class RightBarTheme { this.onDisabled = const Color(0xff313a46), }); - //-------------------------------------- Left Bar Theme ----------------------------------------// - static final RightBarTheme lightRightBarTheme = RightBarTheme( - disabled: const Color(0xffffffff), - onDisabled: const Color(0xffdee2e6), - activeSwitchBorderColor: const Color(0xff727cf5), - inactiveSwitchBorderColor: const Color(0xffdee2e6)); + disabled: const Color(0xffffffff), + onDisabled: const Color(0xffdee2e6), + activeSwitchBorderColor: const Color(0xff727cf5), + inactiveSwitchBorderColor: const Color(0xffdee2e6), + ); static final RightBarTheme darkRightBarTheme = RightBarTheme( - disabled: const Color(0xff444d57), - activeSwitchBorderColor: const Color(0xff727cf5), - inactiveSwitchBorderColor: const Color(0xffdee2e6), - onDisabled: const Color(0xff515a65)); + disabled: const Color(0xff444d57), + activeSwitchBorderColor: const Color(0xff727cf5), + inactiveSwitchBorderColor: const Color(0xffdee2e6), + onDisabled: const Color(0xff515a65), + ); +} + +enum ContentThemeColor { + primary, + secondary, + success, + info, + warning, + danger, + light, + dark, + pink, + green, + red; + + Color get color { + return (AdminTheme.theme.contentTheme.getMappedIntoThemeColor[this]?['color']) ?? Colors.black; + } + + Color get onColor { + return (AdminTheme.theme.contentTheme.getMappedIntoThemeColor[this]?['onColor']) ?? Colors.white; + } } class ContentTheme { @@ -127,44 +117,15 @@ class ContentTheme { final Color purple, onPurple; final Color pink, onPink; final Color red, onRed; - final Color brandRed, onBrandRed; - final Color brandGreen, onBrandGreen; final Color cardBackground, cardShadow, cardBorder, cardText, cardTextMuted; final Color title; final Color disabled, onDisabled; - Map> get getMappedIntoThemeColor { - var c = AdminTheme.theme.contentTheme; - return { - ContentThemeColor.primary: {'color': c.primary, 'onColor': c.onPrimary}, - ContentThemeColor.secondary: { - 'color': c.secondary, - 'onColor': c.onSecondary - }, - ContentThemeColor.success: {'color': c.success, 'onColor': c.onSuccess}, - ContentThemeColor.info: {'color': c.info, 'onColor': c.onInfo}, - ContentThemeColor.warning: {'color': c.warning, 'onColor': c.onWarning}, - ContentThemeColor.danger: {'color': c.danger, 'onColor': c.onDanger}, - ContentThemeColor.light: {'color': c.light, 'onColor': c.onLight}, - ContentThemeColor.dark: {'color': c.dark, 'onColor': c.onDark}, - ContentThemeColor.pink: {'color': c.pink, 'onColor': c.onPink}, - ContentThemeColor.red: {'color': c.red, 'onColor': c.onRed}, - ContentThemeColor.brandRed: { - 'color': c.brandRed, - 'onColor': c.onBrandRed - }, - ContentThemeColor.green: { - 'color': c.brandGreen, - 'onColor': c.onBrandGreen - }, - }; - } - ContentTheme({ this.background = const Color(0xfffafbfe), this.onBackground = const Color(0xffF1F1F2), - this.primary = const Color(0xFF49BF3C), + this.primary = const Color(0xff663399), this.onPrimary = const Color(0xffffffff), this.secondary = const Color(0xff6c757d), this.onSecondary = const Color(0xffffffff), @@ -181,13 +142,11 @@ class ContentTheme { this.dark = const Color(0xff313a46), this.onDark = const Color(0xffffffff), this.purple = const Color(0xff800080), - this.onPurple = const Color(0xffFF0000), - this.pink = const Color(0xffFF1087), + this.onPurple = const Color(0xffffffff), + this.pink = const Color(0xffff1087), this.onPink = const Color(0xffffffff), - this.red = const Color(0xffFF0000), + this.red = const Color(0xffff0000), this.onRed = const Color(0xffffffff), - this.brandRed = const Color.fromARGB(255, 255, 0, 0), - this.onBrandRed = const Color(0xffffffff), this.cardBackground = const Color(0xffffffff), this.cardShadow = const Color(0xffffffff), this.cardBorder = const Color(0xffffffff), @@ -196,46 +155,105 @@ class ContentTheme { this.title = const Color(0xff6c757d), this.disabled = const Color(0xffffffff), this.onDisabled = const Color(0xffffffff), - this.brandGreen = const Color(0xFF49BF3C), - this.onBrandGreen = const Color(0xFFFFFFFF), }); - static final ContentTheme lightContentTheme = ContentTheme( - primary: Color(0xFF49BF3C), - background: const Color(0xfffafbfe), - onBackground: const Color(0xff313a46), - cardBorder: const Color(0xffe8ecf1), - cardBackground: const Color(0xffffffff), - cardShadow: const Color(0xff9aa1ab), - cardText: const Color(0xff6c757d), - title: const Color(0xff6c757d), - cardTextMuted: const Color(0xff98a6ad), - brandRed: const Color.fromARGB(255, 255, 0, 0), - onBrandRed: const Color(0xffffffff), - ); + Map> get getMappedIntoThemeColor { + return { + ContentThemeColor.primary: {'color': primary, 'onColor': onPrimary}, + ContentThemeColor.secondary: {'color': secondary, 'onColor': onSecondary}, + ContentThemeColor.success: {'color': success, 'onColor': onSuccess}, + ContentThemeColor.info: {'color': info, 'onColor': onInfo}, + ContentThemeColor.warning: {'color': warning, 'onColor': onWarning}, + ContentThemeColor.danger: {'color': danger, 'onColor': onDanger}, + ContentThemeColor.light: {'color': light, 'onColor': onLight}, + ContentThemeColor.dark: {'color': dark, 'onColor': onDark}, + ContentThemeColor.pink: {'color': pink, 'onColor': onPink}, + ContentThemeColor.red: {'color': red, 'onColor': onRed}, + }; + } - static final ContentTheme darkContentTheme = ContentTheme( - primary: Color(0xFF49BF3C), - background: const Color(0xff343a40), - onBackground: const Color(0xffF1F1F2), - disabled: const Color(0xff444d57), - onDisabled: const Color(0xff515a65), - cardBorder: const Color(0xff464f5b), - cardBackground: const Color(0xff37404a), - cardShadow: const Color(0xff01030E), - cardText: const Color(0xffaab8c5), - title: const Color(0xffaab8c5), - cardTextMuted: const Color(0xff8391a2), - brandRed: const Color.fromARGB(255, 255, 0, 0), - onBrandRed: const Color(0xffffffff), - ); + ContentTheme copyWith({ + Color? primary, + Color? onPrimary, + Color? secondary, + Color? onSecondary, + Color? background, + Color? onBackground, + }) { + return ContentTheme( + primary: primary ?? this.primary, + onPrimary: onPrimary ?? this.onPrimary, + secondary: secondary ?? this.secondary, + onSecondary: onSecondary ?? this.onSecondary, + background: background ?? this.background, + onBackground: onBackground ?? this.onBackground, + success: success, + onSuccess: onSuccess, + danger: danger, + onDanger: onDanger, + warning: warning, + onWarning: onWarning, + info: info, + onInfo: onInfo, + light: light, + onLight: onLight, + dark: dark, + onDark: onDark, + purple: purple, + onPurple: onPurple, + pink: pink, + onPink: onPink, + red: red, + onRed: onRed, + cardBackground: cardBackground, + cardShadow: cardShadow, + cardBorder: cardBorder, + cardText: cardText, + cardTextMuted: cardTextMuted, + title: title, + disabled: disabled, + onDisabled: onDisabled, + ); + } + + static ContentTheme withColorTheme( + ColorThemeType colorTheme, { + ThemeMode mode = ThemeMode.light, + }) { + final baseTheme = mode == ThemeMode.light + ? ContentTheme() + : ContentTheme( + primary: const Color(0xff32BFAE), + background: const Color(0xff343a40), + onBackground: const Color(0xffF1F1F2), + cardBorder: const Color(0xff464f5b), + cardBackground: const Color(0xff37404a), + cardShadow: const Color(0xff01030E), + cardText: const Color(0xffaab8c5), + title: const Color(0xffaab8c5), + cardTextMuted: const Color(0xff8391a2), + ); + + switch (colorTheme) { + case ColorThemeType.purple: + return baseTheme.copyWith(primary: const Color(0xff663399), onPrimary: Colors.white); + case ColorThemeType.red: + return baseTheme.copyWith(primary: const Color(0xffff0000), onPrimary: Colors.white); + case ColorThemeType.green: + return baseTheme.copyWith(primary: const Color(0xff49BF3C), onPrimary: Colors.white); + case ColorThemeType.blue: + return baseTheme.copyWith(primary: const Color(0xff007bff), onPrimary: Colors.white); + } + } } +enum ColorThemeType { purple, red, green, blue } + class AdminTheme { + final ContentTheme contentTheme; final LeftBarTheme leftBarTheme; final RightBarTheme rightBarTheme; final TopBarTheme topBarTheme; - final ContentTheme contentTheme; AdminTheme({ required this.leftBarTheme, @@ -244,27 +262,22 @@ class AdminTheme { required this.contentTheme, }); - //-------------------------------------- Left Bar Theme ----------------------------------------// - static AdminTheme theme = AdminTheme( - leftBarTheme: LeftBarTheme.lightLeftBarTheme, - topBarTheme: TopBarTheme.lightTopBarTheme, - rightBarTheme: RightBarTheme.lightRightBarTheme, - contentTheme: ContentTheme.lightContentTheme); + leftBarTheme: LeftBarTheme.lightLeftBarTheme, + topBarTheme: TopBarTheme.lightTopBarTheme, + rightBarTheme: RightBarTheme.lightRightBarTheme, + contentTheme: ContentTheme.withColorTheme(ColorThemeType.purple, mode: ThemeMode.light), + ); static void setTheme() { + final themeMode = ThemeCustomizer.instance.theme; + final colorTheme = ThemeCustomizer.instance.colorTheme; + theme = AdminTheme( - leftBarTheme: ThemeCustomizer.instance.theme == ThemeMode.dark - ? LeftBarTheme.darkLeftBarTheme - : LeftBarTheme.lightLeftBarTheme, - topBarTheme: ThemeCustomizer.instance.theme == ThemeMode.dark - ? TopBarTheme.darkTopBarTheme - : TopBarTheme.lightTopBarTheme, - rightBarTheme: ThemeCustomizer.instance.theme == ThemeMode.dark - ? RightBarTheme.darkRightBarTheme - : RightBarTheme.lightRightBarTheme, - contentTheme: ThemeCustomizer.instance.theme == ThemeMode.dark - ? ContentTheme.darkContentTheme - : ContentTheme.lightContentTheme); + leftBarTheme: themeMode == ThemeMode.dark ? LeftBarTheme.darkLeftBarTheme : LeftBarTheme.lightLeftBarTheme, + topBarTheme: themeMode == ThemeMode.dark ? TopBarTheme.darkTopBarTheme : TopBarTheme.lightTopBarTheme, + rightBarTheme: themeMode == ThemeMode.dark ? RightBarTheme.darkRightBarTheme : RightBarTheme.lightRightBarTheme, + contentTheme: ContentTheme.withColorTheme(colorTheme, mode: themeMode), + ); } } diff --git a/lib/helpers/theme/theme_customizer.dart b/lib/helpers/theme/theme_customizer.dart index 4abb4e7..cf6f16a 100644 --- a/lib/helpers/theme/theme_customizer.dart +++ b/lib/helpers/theme/theme_customizer.dart @@ -24,7 +24,7 @@ class ThemeCustomizer { ThemeMode leftBarTheme = ThemeMode.light; ThemeMode rightBarTheme = ThemeMode.light; ThemeMode topBarTheme = ThemeMode.light; - + ColorThemeType colorTheme = ColorThemeType.red; bool rightBarOpen = false; bool leftBarCondensed = false; @@ -73,6 +73,11 @@ class ThemeCustomizer { } } + /// Public method to trigger theme updates externally + static void applyThemeChange() { + _notify(); + } + static void notify() { for (var value in _notifier) { value(oldInstance, instance); diff --git a/lib/helpers/theme/theme_editor_widget.dart b/lib/helpers/theme/theme_editor_widget.dart new file mode 100644 index 0000000..8e6b2b3 --- /dev/null +++ b/lib/helpers/theme/theme_editor_widget.dart @@ -0,0 +1,271 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:marco/helpers/widgets/my_text.dart'; +import 'package:marco/helpers/widgets/wave_background.dart'; +import 'package:marco/helpers/theme/admin_theme.dart'; +import 'package:marco/helpers/theme/theme_customizer.dart'; + +class ThemeOption { + final String label; + final Color primary; + final Color button; + final Color brand; + final ColorThemeType colorThemeType; + + ThemeOption( + this.label, this.primary, this.button, this.brand, this.colorThemeType); +} + +final List themeOptions = [ + ThemeOption( + "Theme 1", Colors.red, Colors.red, Colors.red, ColorThemeType.red), + ThemeOption( + "Theme 2", + const Color(0xFF49BF3C), + const Color(0xFF49BF3C), + const Color(0xFF49BF3C), + ColorThemeType.green, + ), + ThemeOption( + "Theme 3", + const Color(0xFF3F51B5), + const Color(0xFF3F51B5), + const Color(0xFF3F51B5), + ColorThemeType.blue, + ), + ThemeOption( + "Theme 4", + const Color(0xFF663399), + const Color(0xFF663399), + const Color(0xFF663399), + ColorThemeType.purple, + ), +]; + +class ThemeController extends GetxController { + RxInt selectedIndex = 0.obs; + RxBool showApplied = false.obs; + + void init() { + final currentPrimary = AdminTheme.theme.contentTheme.primary; + int index = themeOptions + .indexWhere((opt) => opt.primary.value == currentPrimary.value); + selectedIndex.value = index == -1 ? 0 : index; + } + + void applyTheme(int index) async { + selectedIndex.value = index; + showApplied.value = true; + + ThemeCustomizer.instance.colorTheme = themeOptions[index].colorThemeType; + + ThemeCustomizer.applyThemeChange(); + + await Future.delayed(const Duration(milliseconds: 600)); + showApplied.value = false; + } +} + +class ThemeEditorWidget extends StatefulWidget { + final VoidCallback onClose; + + const ThemeEditorWidget({super.key, required this.onClose}); + + @override + _ThemeEditorWidgetState createState() => _ThemeEditorWidgetState(); +} + +class _ThemeEditorWidgetState extends State { + final ThemeController themeController = Get.put(ThemeController()); + + @override + void initState() { + super.initState(); + themeController.init(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header row with title and close button + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyText.bodyLarge("Theme Customization", fontWeight: 600), + IconButton( + icon: const Icon(Icons.close), + onPressed: widget.onClose, + tooltip: "Back", + iconSize: 20, + ), + ], + ), + const SizedBox(height: 12), + + // Theme cards wrapped in reactive Obx widget + Center( + child: Obx( + () => Wrap( + spacing: 12, + runSpacing: 12, + alignment: WrapAlignment.center, + children: List.generate(themeOptions.length, (i) { + return ThemeCard( + themeOption: themeOptions[i], + isSelected: themeController.selectedIndex.value == i, + onTap: () => themeController.applyTheme(i), + ); + }), + ), + ), + ), + + const SizedBox(height: 12), + + // Applied indicator reactive widget + Obx( + () => themeController.showApplied.value + ? Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.check_circle, + color: + themeOptions[themeController.selectedIndex.value] + .brand, + size: 20, + ), + const SizedBox(width: 6), + Text( + "Theme Applied!", + style: TextStyle( + color: themeOptions[ + themeController.selectedIndex.value] + .brand, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ) + : const SizedBox(), + ), + + const SizedBox(height: 16), + const Text( + "Preview and select a theme. You can change this anytime.", + style: TextStyle(fontSize: 13, color: Colors.black54), + ), + ], + ), + ); + } +} + +class ThemeCard extends StatelessWidget { + final ThemeOption themeOption; + final bool isSelected; + final VoidCallback onTap; + + const ThemeCard({ + Key? key, + required this.themeOption, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 80, + child: Material( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + elevation: isSelected ? 4 : 1, + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected ? themeOption.brand : Colors.transparent, + width: 2, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 80, + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Stack( + fit: StackFit.expand, + children: [ + CustomPaint( + painter: RedWavePainter(themeOption.brand, 0.15)), + Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Hello, User!", + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.w600, + color: themeOption.primary, + fontSize: 12, + ), + ), + const SizedBox(height: 4), + SizedBox( + height: 18, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: themeOption.button, + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + elevation: 1, + textStyle: const TextStyle(fontSize: 10), + ), + onPressed: () {}, + child: const Text("Welcome"), + ), + ), + ], + ), + ), + ], + ), + ), + ), + const SizedBox(height: 6), + Text( + themeOption.label, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 12, + color: Colors.grey[700], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/helpers/widgets/wave_background.dart b/lib/helpers/widgets/wave_background.dart index d4c408e..fcb2584 100644 --- a/lib/helpers/widgets/wave_background.dart +++ b/lib/helpers/widgets/wave_background.dart @@ -1,38 +1,35 @@ import 'package:flutter/material.dart'; -class WaveBackground extends StatelessWidget { - final Color color; +class RedWaveBackground extends StatelessWidget { + final Color brandRed; final double heightFactor; - const WaveBackground({ + const RedWaveBackground({ super.key, - required this.color, - this.heightFactor = 0.2, + required this.brandRed, + this.heightFactor = 0.2, }); @override Widget build(BuildContext context) { return CustomPaint( - painter: _WavePainter(color, heightFactor), + painter: RedWavePainter(brandRed, heightFactor), size: Size.infinite, ); } } -class _WavePainter extends CustomPainter { - final Color color; +class RedWavePainter extends CustomPainter { + final Color brandRed; final double heightFactor; - _WavePainter(this.color, this.heightFactor); + RedWavePainter(this.brandRed, this.heightFactor); @override void paint(Canvas canvas, Size size) { final paint1 = Paint() ..shader = LinearGradient( - colors: [ - const Color(0xFF49BF3C), - const Color(0xFF81C784), - ], + colors: [brandRed, const Color.fromARGB(255, 97, 22, 22)], begin: Alignment.topLeft, end: Alignment.bottomRight, ).createShader(Rect.fromLTWH(0, 0, size.width, size.height)); @@ -40,20 +37,26 @@ class _WavePainter extends CustomPainter { final path1 = Path() ..moveTo(0, size.height * heightFactor) ..quadraticBezierTo( - size.width * 0.25, size.height * 0.05, size.width * 0.5, size.height * 0.15) + size.width * 0.25, size.height * 0.05, + size.width * 0.5, size.height * 0.15, + ) ..quadraticBezierTo( - size.width * 0.75, size.height * 0.25, size.width, size.height * 0.1) + size.width * 0.75, size.height * 0.25, + size.width, size.height * 0.1, + ) ..lineTo(size.width, 0) ..lineTo(0, 0) ..close(); canvas.drawPath(path1, paint1); - // Secondary wave (overlay) with same green but lighter opacity - final paint2 = Paint()..color = const Color(0xFF49BF3C).withOpacity(0.15); + final paint2 = Paint()..color = brandRed.withOpacity(0.15); final path2 = Path() ..moveTo(0, size.height * (heightFactor + 0.05)) - ..quadraticBezierTo(size.width * 0.4, size.height * 0.1, size.width, size.height * 0.2) + ..quadraticBezierTo( + size.width * 0.4, size.height * 0.1, + size.width, size.height * 0.2, + ) ..lineTo(size.width, 0) ..lineTo(0, 0) ..close(); diff --git a/lib/view/auth/forgot_password_screen.dart b/lib/view/auth/forgot_password_screen.dart index 1f61e69..d9e8150 100644 --- a/lib/view/auth/forgot_password_screen.dart +++ b/lib/view/auth/forgot_password_screen.dart @@ -60,7 +60,7 @@ class _ForgotPasswordScreenState extends State return Scaffold( body: Stack( children: [ - WaveBackground(color: contentTheme.brandRed), + RedWaveBackground(brandRed: contentTheme.primary), SafeArea( child: Center( child: Column( @@ -254,7 +254,7 @@ class _ForgotPasswordScreenState extends State Widget _buildBackButton() { return TextButton.icon( onPressed: () async => await LocalStorage.logout(), - icon: Icon(Icons.arrow_back, size: 18, color: contentTheme.primary), + icon: Icon(Icons.arrow_back, size: 18, color: contentTheme.primary,), label: MyText.bodyMedium( 'Back to Login', color: contentTheme.primary, diff --git a/lib/view/auth/login_option_screen.dart b/lib/view/auth/login_option_screen.dart index dee7e7b..2f08def 100644 --- a/lib/view/auth/login_option_screen.dart +++ b/lib/view/auth/login_option_screen.dart @@ -56,7 +56,7 @@ class _WelcomeScreenState extends State context: context, barrierDismissible: false, builder: (_) => Dialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), insetPadding: const EdgeInsets.all(24), child: SingleChildScrollView( padding: const EdgeInsets.all(24), @@ -102,7 +102,7 @@ class _WelcomeScreenState extends State return Scaffold( body: Stack( children: [ - WaveBackground(color: contentTheme.brandRed), + RedWaveBackground(brandRed: contentTheme.primary), SafeArea( child: Center( child: SingleChildScrollView( @@ -204,7 +204,7 @@ class _WelcomeScreenState extends State padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.orangeAccent, - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(6), ), child: MyText( 'BETA', @@ -235,9 +235,10 @@ class _WelcomeScreenState extends State ), ), style: ElevatedButton.styleFrom( - backgroundColor: contentTheme.brandGreen, + backgroundColor: contentTheme.primary, foregroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), elevation: 4, shadowColor: Colors.black26, ), diff --git a/lib/view/auth/login_screen.dart b/lib/view/auth/login_screen.dart index 93b8fd2..025be02 100644 --- a/lib/view/auth/login_screen.dart +++ b/lib/view/auth/login_screen.dart @@ -193,7 +193,7 @@ class _LoginScreenState extends State with UIMixin { elevation: 2, padding: MySpacing.xy(24, 16), borderRadiusAll: 5, - backgroundColor:contentTheme.brandGreen, + backgroundColor:contentTheme.primary, child: MyText.labelMedium( 'Login', fontWeight: 600, diff --git a/lib/view/auth/mpin_auth_screen.dart b/lib/view/auth/mpin_auth_screen.dart index 8808f54..fdef51d 100644 --- a/lib/view/auth/mpin_auth_screen.dart +++ b/lib/view/auth/mpin_auth_screen.dart @@ -10,6 +10,7 @@ import 'package:marco/helpers/services/api_endpoints.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/widgets/wave_background.dart'; + class MPINAuthScreen extends StatefulWidget { const MPINAuthScreen({super.key}); @@ -52,7 +53,7 @@ class _MPINAuthScreenState extends State return Scaffold( body: Stack( children: [ - WaveBackground(color: contentTheme.brandRed), + RedWaveBackground(brandRed: contentTheme.primary), SafeArea( child: Center( child: Column( @@ -111,7 +112,7 @@ class _MPINAuthScreenState extends State horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.orangeAccent, - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(6), ), child: MyText( 'BETA', @@ -146,7 +147,7 @@ class _MPINAuthScreenState extends State padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(20), boxShadow: const [ BoxShadow( color: Colors.black12, @@ -265,7 +266,7 @@ class _MPINAuthScreenState extends State filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), @@ -280,7 +281,7 @@ class _MPINAuthScreenState extends State onPressed: controller.isLoading.value ? null : controller.onSubmitMPIN, elevation: 2, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), - borderRadiusAll: 5, + borderRadiusAll: 10, backgroundColor: controller.isLoading.value ? contentTheme.primary.withOpacity(0.6) : contentTheme.primary, @@ -316,11 +317,11 @@ class _MPINAuthScreenState extends State if (isNewUser || isChangeMpin) TextButton.icon( onPressed: () => Get.toNamed('/dashboard'), - icon: const Icon(Icons.arrow_back, - size: 18, color: Colors.redAccent), + icon: Icon(Icons.arrow_back, + size: 18, color: contentTheme.primary), label: MyText.bodyMedium( 'Back to Home Page', - color: contentTheme.brandRed, + color: contentTheme.primary, fontWeight: 600, fontSize: 14, ), @@ -332,7 +333,7 @@ class _MPINAuthScreenState extends State size: 18, color: Colors.redAccent), label: MyText.bodyMedium( 'Go back to Login Screen', - color: contentTheme.brandRed, + color: contentTheme.primary, fontWeight: 600, fontSize: 14, ), diff --git a/lib/view/auth/otp_login_form.dart b/lib/view/auth/otp_login_form.dart index 9dff549..e8b75b0 100644 --- a/lib/view/auth/otp_login_form.dart +++ b/lib/view/auth/otp_login_form.dart @@ -170,7 +170,7 @@ class _OTPLoginScreenState extends State with UIMixin { ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: contentTheme.brandRed, width: 2), + borderSide: BorderSide(color: contentTheme.primary, width: 2), ), ), ), @@ -220,7 +220,7 @@ class _OTPLoginScreenState extends State with UIMixin { elevation: 2, padding: MySpacing.xy(24, 16), borderRadiusAll: 10, - backgroundColor: contentTheme.brandRed, + backgroundColor: contentTheme.primary, child: MyText.labelMedium( 'Verify OTP', fontWeight: 600, diff --git a/lib/view/auth/request_demo_bottom_sheet.dart b/lib/view/auth/request_demo_bottom_sheet.dart index 4bcddb4..d7f839d 100644 --- a/lib/view/auth/request_demo_bottom_sheet.dart +++ b/lib/view/auth/request_demo_bottom_sheet.dart @@ -4,6 +4,7 @@ import 'package:marco/helpers/services/auth_service.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/widgets/my_text.dart'; +import 'package:marco/helpers/widgets/my_spacing.dart'; class OrganizationFormBottomSheet { static void show(BuildContext context) { @@ -81,169 +82,203 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin { @override Widget build(BuildContext context) { - return Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), - ), - padding: const EdgeInsets.fromLTRB(20, 20, 20, 40), - child: SingleChildScrollView( - controller: widget.scrollController, - child: Form( - key: validator.formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center( - child: Container( - width: 40, - height: 5, - margin: const EdgeInsets.only(bottom: 20), - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(10), - ), - ), - ), - Center( - child: Column( - children: [ - MyText.titleLarge( - 'Adventure starts here 🚀', - fontWeight: 600, - color: Colors.black87, - ), - const SizedBox(height: 4), - MyText.bodySmall( - "Make your app management easy and fun!", - color: Colors.grey, - ), - ], - ), - ), - const SizedBox(height: 20), - _sectionHeader('Organization Info'), - _buildTextField('organizationName', 'Organization Name'), - _buildTextField('email', 'Email', - keyboardType: TextInputType.emailAddress), - _buildTextField('about', 'About Organization'), - _sectionHeader('Contact Details'), - _buildTextField('contactPerson', 'Contact Person'), - _buildTextField('contactNumber', 'Contact Number', - keyboardType: TextInputType.phone), - _buildTextField('address', 'Current Address'), - _sectionHeader('Additional Details'), - _buildPopupMenuField( - 'Organization Size', - _sizes, - _selectedSize, - (val) => setState(() => _selectedSize = val), - 'Please select organization size', - ), - _buildPopupMenuField( - 'Industry', - _industries.map((e) => e['name'] as String).toList(), - _selectedIndustryId != null - ? _industries.firstWhere( - (e) => e['id'] == _selectedIndustryId)['name'] - : null, - (val) { - setState(() { - final selectedIndustry = _industries.firstWhere( - (element) => element['name'] == val, - orElse: () => {}, - ); - _selectedIndustryId = selectedIndustry['id']; - }); - }, - 'Please select industry', - ), - const SizedBox(height: 12), - Row( - children: [ - Checkbox( - value: _agreed, - onChanged: (val) => setState(() => _agreed = val ?? false), - fillColor: MaterialStateProperty.resolveWith((states) => - states.contains(MaterialState.selected) - ? contentTheme.primary - : Colors.white), - checkColor: Colors.white, - ), - Row( - children: [ - MyText( - 'I agree to the ', - color: Colors.black87, - ), - MyText( - 'privacy policy & terms', - color: contentTheme.primary, - fontWeight: 600, - ), - ], - ) - ], - ), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.close, color: Colors.white), - label: MyText.bodyMedium( - "Cancel", - color: Colors.white, - fontWeight: 600, - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - padding: const EdgeInsets.symmetric(vertical: 8), - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: ElevatedButton.icon( - onPressed: _loading ? null : _submitForm, - icon: - Icon(Icons.check_circle_outline, color: Colors.white), - label: MyText.bodyMedium( - _loading ? "Submitting..." : "Submit", - color: Colors.white, - fontWeight: 600, - ), - style: ElevatedButton.styleFrom( - backgroundColor: contentTheme - .primary, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - padding: const EdgeInsets.symmetric(vertical: 8), - ), - ), - ), - ], - ), - const SizedBox(height: 8), - Center( - child: TextButton.icon( - onPressed: () => Navigator.pop(context), - icon: - Icon(Icons.arrow_back, size: 18, color: contentTheme.primary), - label: MyText.bodySmall( - 'Back to log in', - fontWeight: 600, - color: contentTheme.primary, - ), - ), + return SingleChildScrollView( + padding: MediaQuery.of(context).viewInsets, + child: Padding( + padding: const EdgeInsets.only(top: 60), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 12, + offset: Offset(0, -2), ), ], ), + child: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(20, 16, 20, 32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 40, + height: 5, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(10), + ), + ), + ), + MySpacing.height(12), + Center( + child: MyText.titleLarge( + 'Adventure starts here 🚀', + fontWeight: 700, + textAlign: TextAlign.center, + ), + ), + MySpacing.height(4), + Center( + child: MyText.bodySmall( + "Make your app management easy and fun!", + color: Colors.grey[700], + ), + ), + MySpacing.height(12), + Form( + key: validator.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _sectionHeader('Organization Info'), + _buildTextField('organizationName', 'Organization Name'), + _buildTextField('email', 'Email', + keyboardType: TextInputType.emailAddress), + _buildTextField('about', 'About Organization'), + _sectionHeader('Contact Details'), + _buildTextField('contactPerson', 'Contact Person'), + _buildTextField('contactNumber', 'Contact Number', + keyboardType: TextInputType.phone), + _buildTextField('address', 'Current Address'), + _sectionHeader('Additional Details'), + _buildPopupMenuField( + 'Organization Size', + _sizes, + _selectedSize, + (val) => setState(() => _selectedSize = val), + 'Please select organization size', + ), + _buildPopupMenuField( + 'Industry', + _industries.map((e) => e['name'] as String).toList(), + _selectedIndustryId != null + ? _industries.firstWhere( + (e) => e['id'] == _selectedIndustryId)['name'] + : null, + (val) { + setState(() { + final selectedIndustry = _industries.firstWhere( + (element) => element['name'] == val, + orElse: () => {}, + ); + _selectedIndustryId = selectedIndustry['id']; + }); + }, + 'Please select industry', + ), + const SizedBox(height: 12), + Row( + children: [ + Checkbox( + value: _agreed, + onChanged: (val) => + setState(() => _agreed = val ?? false), + fillColor: MaterialStateProperty.resolveWith( + (states) => states.contains(MaterialState.selected) + ? contentTheme.primary + : Colors.white), + checkColor: Colors.white, + side: BorderSide(color: contentTheme.primary, width: 2), + ), + Flexible( + child: Wrap( + children: [ + MyText( + 'I agree to the ', + color: Colors.black87, + ), + MyText( + 'privacy policy & terms', + color: contentTheme.primary, + fontWeight: 600, + ), + ], + ), + ), + ], + ), + ], + ), + ), + MySpacing.height(12), + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () => Navigator.pop(context), + icon: const Icon(Icons.close, color: Colors.white), + label: MyText.bodyMedium( + "Cancel", + color: Colors.white, + fontWeight: 600, + ), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton.icon( + onPressed: _loading ? null : _submitForm, + icon: _loading + ? SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : const Icon(Icons.check_circle_outline, + color: Colors.white), + label: MyText.bodyMedium( + _loading ? "Submitting..." : "Submit", + color: Colors.white, + fontWeight: 600, + ), + style: ElevatedButton.styleFrom( + backgroundColor: contentTheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + ), + ), + ], + ), + MySpacing.height(12), + Center( + child: TextButton.icon( + onPressed: () => Navigator.pop(context), + icon: Icon( + Icons.arrow_back, + size: 18, + color: contentTheme.primary, + ), + label: MyText.bodySmall( + 'Back to log in', + fontWeight: 600, + color: contentTheme.primary, + ), + ), + ), + ], + ), + ), + ), ), ), ); @@ -290,19 +325,19 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin { contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey[400]!), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey[300]!), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), - borderSide: BorderSide(color: contentTheme.brandRed, width: 1.5), + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: contentTheme.primary, width: 1.5), ), errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.red), ), ), @@ -366,17 +401,17 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin { contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey[400]!), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey[300]!), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), borderSide: - BorderSide(color: contentTheme.brandRed, width: 1.5), + BorderSide(color: contentTheme.primary, width: 1.5), ), errorText: fieldState.errorText, ), @@ -385,8 +420,7 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin { children: [ MyText.bodyMedium( selectedValue ?? 'Select $label', - color: - selectedValue == null ? Colors.grey : Colors.black, + color: selectedValue == null ? Colors.grey : Colors.black, ), const Icon(Icons.arrow_drop_down, color: Colors.grey), ], diff --git a/lib/view/directory/notes_view.dart b/lib/view/directory/notes_view.dart index 00f579e..8865afa 100644 --- a/lib/view/directory/notes_view.dart +++ b/lib/view/directory/notes_view.dart @@ -134,7 +134,7 @@ class _NotesViewState extends State with UIMixin { ), if (note.isActive) ...[ IconButton( - icon: Icon(isEditing ? Icons.close : Icons.edit_outlined, + icon: Icon(isEditing ? Icons.close : Icons.edit , color: Colors.indigo, size: 18), splashRadius: 18, onPressed: () { @@ -143,7 +143,7 @@ class _NotesViewState extends State with UIMixin { }, ), IconButton( - icon: const Icon(Icons.delete_outline, + icon: const Icon(Icons.delete, size: 18, color: Colors.red), splashRadius: 18, onPressed: () async { @@ -216,7 +216,7 @@ class _NotesViewState extends State with UIMixin { await controller.updateNote(updated); controller.editingNoteId.value = null; }, - icon: const Icon(Icons.check_circle_outline, + icon: const Icon(Icons.check_circle, color: Colors.white), label: MyText.bodyMedium( "Save", diff --git a/lib/view/layouts/layout.dart b/lib/view/layouts/layout.dart index 10405c3..d298b64 100644 --- a/lib/view/layouts/layout.dart +++ b/lib/view/layouts/layout.dart @@ -9,6 +9,7 @@ import 'package:marco/helpers/services/api_endpoints.dart'; import 'package:marco/images.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/view/layouts/user_profile_right_bar.dart'; +import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; class Layout extends StatefulWidget { final Widget? child; @@ -20,7 +21,7 @@ class Layout extends StatefulWidget { State createState() => _LayoutState(); } -class _LayoutState extends State { +class _LayoutState extends State with UIMixin { final LayoutController controller = LayoutController(); final EmployeeInfo? employeeInfo = LocalStorage.getEmployeeInfo(); final bool isBetaEnvironment = ApiEndpoints.baseUrl.contains("stage"); @@ -409,13 +410,13 @@ class _LayoutState extends State { style: TextStyle( fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - color: isSelected ? Colors.blueAccent : Colors.black87, + color: isSelected ? contentTheme.primary : Colors.black87, ), ), contentPadding: const EdgeInsets.symmetric(horizontal: 0), - activeColor: Colors.blueAccent, + activeColor: contentTheme.primary, tileColor: isSelected - ? Colors.blueAccent.withOpacity(0.1) + ? contentTheme.primary.withOpacity(0.1) : Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), diff --git a/lib/view/layouts/offline_screen.dart b/lib/view/layouts/offline_screen.dart index c5d4a77..0adc165 100644 --- a/lib/view/layouts/offline_screen.dart +++ b/lib/view/layouts/offline_screen.dart @@ -39,7 +39,7 @@ class _OfflineScreenState extends State return Scaffold( body: Stack( children: [ - _RedWaveBackground(brandRed: contentTheme.brandRed), + _RedWaveBackground(primary: contentTheme.primary), SafeArea( child: Center( child: Padding( @@ -101,28 +101,28 @@ class _OfflineScreenState extends State } class _RedWaveBackground extends StatelessWidget { - final Color brandRed; - const _RedWaveBackground({required this.brandRed}); + final Color primary; + const _RedWaveBackground({required this.primary}); @override Widget build(BuildContext context) { return CustomPaint( - painter: _WavePainter(brandRed), + painter: _WavePainter(primary), size: Size.infinite, ); } } class _WavePainter extends CustomPainter { - final Color brandRed; + final Color primary; - _WavePainter(this.brandRed); + _WavePainter(this.primary); @override void paint(Canvas canvas, Size size) { final paint1 = Paint() ..shader = LinearGradient( - colors: [brandRed, const Color.fromARGB(255, 97, 22, 22)], + colors: [primary, const Color.fromARGB(255, 97, 22, 22)], begin: Alignment.topLeft, end: Alignment.bottomRight, ).createShader(Rect.fromLTWH(0, 0, size.width, size.height)); diff --git a/lib/view/layouts/user_profile_right_bar.dart b/lib/view/layouts/user_profile_right_bar.dart index e0fe80e..63bc6b2 100644 --- a/lib/view/layouts/user_profile_right_bar.dart +++ b/lib/view/layouts/user_profile_right_bar.dart @@ -10,8 +10,8 @@ import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/model/employees/employee_info.dart'; import 'package:marco/controller/auth/mpin_controller.dart'; import 'package:marco/view/employees/employee_profile_screen.dart'; -import 'package:marco/view/support/support_screen.dart'; -import 'package:marco/view/faq/faq_screen.dart'; +import 'package:marco/helpers/theme/theme_editor_widget.dart'; + class UserProfileBar extends StatefulWidget { final bool isCondensed; @@ -26,6 +26,7 @@ class _UserProfileBarState extends State late EmployeeInfo employeeInfo; bool _isLoading = true; bool hasMpin = true; + bool _isThemeEditorVisible = false; @override void initState() { @@ -45,7 +46,7 @@ class _UserProfileBarState extends State return Padding( padding: const EdgeInsets.only(left: 14), child: ClipRRect( - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(22), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 18, sigmaY: 18), child: AnimatedContainer( @@ -55,59 +56,72 @@ class _UserProfileBarState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: [ - Colors.white.withValues(alpha: 0.95), - Colors.white.withValues(alpha: 0.85), + Colors.white.withOpacity(0.95), + Colors.white.withOpacity(0.85), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(22), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.06), + color: Colors.black.withOpacity(0.06), blurRadius: 18, offset: const Offset(0, 8), ) ], border: Border.all( - color: Colors.grey.withValues(alpha: 0.25), + color: Colors.grey.withOpacity(0.25), width: 1, ), ), child: SafeArea( - bottom: true, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _isLoading - ? const _LoadingSection() - : _userProfileSection(isCondensed), - MySpacing.height(12), - Divider( - indent: 18, - endIndent: 18, - thickness: 0.7, - color: Colors.grey.withValues(alpha: 0.25), - ), - MySpacing.height(12), - _supportAndSettingsMenu(isCondensed), - const Spacer(), - Divider( - indent: 18, - endIndent: 18, - thickness: 0.35, - color: Colors.grey.withValues(alpha: 0.18), - ), - _logoutButton(isCondensed), - ], - ), - ), + bottom: true, + child: Stack( + children: [ + Offstage( + offstage: _isThemeEditorVisible, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _isLoading + ? const _LoadingSection() + : _userProfileSection(isCondensed), + MySpacing.height(12), + Divider( + indent: 18, + endIndent: 18, + thickness: 0.7, + color: Colors.grey.withOpacity(0.25), + ), + MySpacing.height(12), + _supportAndSettingsMenu(isCondensed), + const Spacer(), + Divider( + indent: 18, + endIndent: 18, + thickness: 0.35, + color: Colors.grey.withOpacity(0.18), + ), + _logoutButton(isCondensed), + ], + ), + ), + Offstage( + offstage: !_isThemeEditorVisible, + child: ThemeEditorWidget( + onClose: () { + setState(() => _isThemeEditorVisible = false); + }, + ), + ), + ], + )), ), ), ), ); } - Widget _userProfileSection(bool condensed) { final padding = MySpacing.fromLTRB( condensed ? 16 : 26, @@ -126,7 +140,7 @@ class _UserProfileBarState extends State shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Theme.of(context).primaryColor.withValues(alpha: 0.15), + color: Theme.of(context).primaryColor.withOpacity(0.15), blurRadius: 10, spreadRadius: 1, ), @@ -180,22 +194,23 @@ class _UserProfileBarState extends State ), SizedBox(height: spacingHeight), _menuItemRow( - icon: LucideIcons.badge_help, - label: 'Support', - onTap: _onSupportTap, + icon: LucideIcons.settings, + label: 'Settings', + onTap: () { + setState(() { + _isThemeEditorVisible = true; + }); + }, ), SizedBox(height: spacingHeight), _menuItemRow( - icon: LucideIcons.info, - label: 'FAQ', // <-- New FAQ menu item - onTap: _onFaqTap, // <-- Handle tap + icon: LucideIcons.badge_alert, + label: 'Support', ), SizedBox(height: spacingHeight), _menuItemRow( icon: LucideIcons.lock, label: hasMpin ? 'Change MPIN' : 'Set MPIN', - iconColor: contentTheme.primary, - textColor: contentTheme.primary, onTap: _onMpinTap, ), ], @@ -203,14 +218,6 @@ class _UserProfileBarState extends State ); } - void _onFaqTap() { - Get.to(() => const FAQScreen()); - } - - void _onSupportTap() { - Get.to(() => const SupportScreen()); - } - Widget _menuItemRow({ required IconData icon, required String label, @@ -220,12 +227,12 @@ class _UserProfileBarState extends State }) { return InkWell( onTap: onTap, - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: Colors.white.withOpacity(0.9), - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.withOpacity(0.2), width: 1), ), child: Row( @@ -277,15 +284,15 @@ class _UserProfileBarState extends State fontWeight: 700, ), style: ElevatedButton.styleFrom( - backgroundColor: Colors.red.shade600, + backgroundColor: contentTheme.primary, foregroundColor: Colors.white, - shadowColor: Colors.red.shade200, + shadowColor: contentTheme.primary, padding: EdgeInsets.symmetric( - vertical: condensed ? 9 : 12, - horizontal: condensed ? 6 : 16, + vertical: condensed ? 14 : 18, + horizontal: condensed ? 14 : 22, ), shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), ), @@ -301,54 +308,71 @@ class _UserProfileBarState extends State } Widget _buildLogoutDialog(BuildContext context) { + final theme = Theme.of(context); + final primaryColor = contentTheme.primary; + return Dialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), elevation: 10, - backgroundColor: Colors.white, + backgroundColor: theme.cardColor, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 34), child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(LucideIcons.log_out, size: 56, color: Colors.red.shade700), - const SizedBox(height: 18), - const Text( + // Top icon + Icon(LucideIcons.log_out, size: 56, color: primaryColor), + MySpacing.height(18), + // Title + MyText.titleLarge( "Logout Confirmation", - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.w700, - color: Colors.black87), - ), - const SizedBox(height: 14), - const Text( - "Are you sure you want to logout?\nYou will need to login again to continue.", + fontWeight: 700, textAlign: TextAlign.center, - style: TextStyle(fontSize: 16, color: Colors.black54), ), - const SizedBox(height: 30), + MySpacing.height(14), + // Subtitle + MyText.bodyMedium( + "Are you sure you want to logout?\nYou will need to login again to continue.", + color: Colors.grey[700], + textAlign: TextAlign.center, + ), + MySpacing.height(30), + // Buttons Row( children: [ Expanded( - child: TextButton( + child: ElevatedButton( onPressed: () => Navigator.pop(context, false), - style: TextButton.styleFrom( - foregroundColor: Colors.grey.shade700, - padding: const EdgeInsets.symmetric(vertical: 12)), - child: const Text("Cancel"), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14)), + ), + child: MyText.bodyMedium( + "Cancel", + color: Colors.white, + fontWeight: 600, + ), ), ), - const SizedBox(width: 18), + MySpacing.width(18), Expanded( child: ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( - backgroundColor: Colors.red.shade700, + backgroundColor: primaryColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5)), + borderRadius: BorderRadius.circular(14)), + ), + child: MyText.bodyMedium( + "Logout", + color: Colors.white, + fontWeight: 600, ), - child: const Text("Logout"), ), ), ],