Refactor UI components to use contentTheme colors
- Updated various screens to replace hardcoded color values with contentTheme.buttonColor for consistency. - Changed icons in NotesView, UserDocumentsPage, EmployeeDetailPage, EmployeesScreen, ExpenseDetailScreen, and others to use updated icon styles. - Refactored OfflineScreen and TenantSelectionScreen to utilize new wave background widget. - Introduced ThemeEditorWidget in UserProfileBar for dynamic theme adjustments. - Enhanced logout confirmation dialog styling to align with new theme colors.
This commit is contained in:
parent
97b45ebd91
commit
04da062f4f
@ -17,16 +17,20 @@ enum ContentThemeColor {
|
|||||||
light,
|
light,
|
||||||
dark,
|
dark,
|
||||||
pink,
|
pink,
|
||||||
green,
|
|
||||||
red,
|
red,
|
||||||
brandRed;
|
brand,
|
||||||
|
button;
|
||||||
|
|
||||||
Color get color {
|
Color get color {
|
||||||
return (AdminTheme.theme.contentTheme.getMappedIntoThemeColor[this]?['color']) ?? Colors.black;
|
return (AdminTheme.theme.contentTheme.getMappedIntoThemeColor[this]
|
||||||
|
?['color']) ??
|
||||||
|
Colors.black;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color get onColor {
|
Color get onColor {
|
||||||
return (AdminTheme.theme.contentTheme.getMappedIntoThemeColor[this]?['onColor']) ?? Colors.white;
|
return (AdminTheme.theme.contentTheme.getMappedIntoThemeColor[this]
|
||||||
|
?['onColor']) ??
|
||||||
|
Colors.white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,8 +47,6 @@ class LeftBarTheme {
|
|||||||
this.activeItemBackground = const Color(0x15663399),
|
this.activeItemBackground = const Color(0x15663399),
|
||||||
});
|
});
|
||||||
|
|
||||||
//-------------------------------------- Left Bar Theme ----------------------------------------//
|
|
||||||
|
|
||||||
static final LeftBarTheme lightLeftBarTheme = LeftBarTheme();
|
static final LeftBarTheme lightLeftBarTheme = LeftBarTheme();
|
||||||
|
|
||||||
static final LeftBarTheme darkLeftBarTheme = LeftBarTheme(
|
static final LeftBarTheme darkLeftBarTheme = LeftBarTheme(
|
||||||
@ -73,11 +75,11 @@ class TopBarTheme {
|
|||||||
this.onBackground = const Color(0xff313a46),
|
this.onBackground = const Color(0xff313a46),
|
||||||
});
|
});
|
||||||
|
|
||||||
//-------------------------------------- Left Bar Theme ----------------------------------------//
|
|
||||||
|
|
||||||
static final TopBarTheme lightTopBarTheme = TopBarTheme();
|
static final TopBarTheme lightTopBarTheme = TopBarTheme();
|
||||||
|
|
||||||
static final TopBarTheme darkTopBarTheme = TopBarTheme(background: const Color(0xff2c3036), onBackground: const Color(0xffdcdcdc));
|
static final TopBarTheme darkTopBarTheme = TopBarTheme(
|
||||||
|
background: const Color(0xff2c3036),
|
||||||
|
onBackground: const Color(0xffdcdcdc));
|
||||||
}
|
}
|
||||||
|
|
||||||
class RightBarTheme {
|
class RightBarTheme {
|
||||||
@ -91,8 +93,6 @@ class RightBarTheme {
|
|||||||
this.onDisabled = const Color(0xff313a46),
|
this.onDisabled = const Color(0xff313a46),
|
||||||
});
|
});
|
||||||
|
|
||||||
//-------------------------------------- Left Bar Theme ----------------------------------------//
|
|
||||||
|
|
||||||
static final RightBarTheme lightRightBarTheme = RightBarTheme(
|
static final RightBarTheme lightRightBarTheme = RightBarTheme(
|
||||||
disabled: const Color(0xffffffff),
|
disabled: const Color(0xffffffff),
|
||||||
onDisabled: const Color(0xffdee2e6),
|
onDisabled: const Color(0xffdee2e6),
|
||||||
@ -117,20 +117,24 @@ class ContentTheme {
|
|||||||
final Color info, onInfo;
|
final Color info, onInfo;
|
||||||
final Color light, onLight;
|
final Color light, onLight;
|
||||||
final Color dark, onDark;
|
final Color dark, onDark;
|
||||||
final Color purple, onPurple;
|
|
||||||
final Color pink, onPink;
|
final Color pink, onPink;
|
||||||
final Color red, onRed;
|
final Color red, onRed;
|
||||||
final Color brandRed, onBrandRed;
|
|
||||||
|
final Color brandColor, onBrandColor;
|
||||||
|
final Color buttonColor, onButtonColor;
|
||||||
|
|
||||||
final Color cardBackground, cardShadow, cardBorder, cardText, cardTextMuted;
|
final Color cardBackground, cardShadow, cardBorder, cardText, cardTextMuted;
|
||||||
final Color title;
|
final Color title;
|
||||||
final Color disabled, onDisabled;
|
final Color disabled, onDisabled;
|
||||||
|
|
||||||
Map<ContentThemeColor, Map<String, Color>> get getMappedIntoThemeColor {
|
Map<ContentThemeColor, Map<String, Color>> get getMappedIntoThemeColor {
|
||||||
var c = AdminTheme.theme.contentTheme;
|
var c = this;
|
||||||
return {
|
return {
|
||||||
ContentThemeColor.primary: {'color': c.primary, 'onColor': c.onPrimary},
|
ContentThemeColor.primary: {'color': c.primary, 'onColor': c.onPrimary},
|
||||||
ContentThemeColor.secondary: {'color': c.secondary, 'onColor': c.onSecondary},
|
ContentThemeColor.secondary: {
|
||||||
|
'color': c.secondary,
|
||||||
|
'onColor': c.onSecondary
|
||||||
|
},
|
||||||
ContentThemeColor.success: {'color': c.success, 'onColor': c.onSuccess},
|
ContentThemeColor.success: {'color': c.success, 'onColor': c.onSuccess},
|
||||||
ContentThemeColor.info: {'color': c.info, 'onColor': c.onInfo},
|
ContentThemeColor.info: {'color': c.info, 'onColor': c.onInfo},
|
||||||
ContentThemeColor.warning: {'color': c.warning, 'onColor': c.onWarning},
|
ContentThemeColor.warning: {'color': c.warning, 'onColor': c.onWarning},
|
||||||
@ -139,37 +143,44 @@ class ContentTheme {
|
|||||||
ContentThemeColor.dark: {'color': c.dark, 'onColor': c.onDark},
|
ContentThemeColor.dark: {'color': c.dark, 'onColor': c.onDark},
|
||||||
ContentThemeColor.pink: {'color': c.pink, 'onColor': c.onPink},
|
ContentThemeColor.pink: {'color': c.pink, 'onColor': c.onPink},
|
||||||
ContentThemeColor.red: {'color': c.red, 'onColor': c.onRed},
|
ContentThemeColor.red: {'color': c.red, 'onColor': c.onRed},
|
||||||
ContentThemeColor.brandRed: {'color': c.brandRed, 'onColor': c.onBrandRed},
|
ContentThemeColor.brand: {
|
||||||
|
'color': c.brandColor,
|
||||||
|
'onColor': c.onBrandColor
|
||||||
|
},
|
||||||
|
ContentThemeColor.button: {
|
||||||
|
'color': c.buttonColor,
|
||||||
|
'onColor': c.onButtonColor
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentTheme({
|
ContentTheme({
|
||||||
this.background = const Color(0xfffafbfe),
|
this.background = const Color(0xfffafbfe),
|
||||||
this.onBackground = const Color(0xffF1F1F2),
|
this.onBackground = const Color(0xff313a46),
|
||||||
this.primary = const Color(0xff663399),
|
this.primary = const Color(0xFF49BF3C),
|
||||||
this.onPrimary = const Color(0xffffffff),
|
this.onPrimary = Colors.white,
|
||||||
this.secondary = const Color(0xff6c757d),
|
this.secondary = const Color(0xff6c757d),
|
||||||
this.onSecondary = const Color(0xffffffff),
|
this.onSecondary = Colors.white,
|
||||||
this.success = const Color(0xff00be82),
|
this.success = const Color(0xff00be82),
|
||||||
this.onSuccess = const Color(0xffffffff),
|
this.onSuccess = Colors.white,
|
||||||
this.danger = const Color(0xffdc3545),
|
this.danger = const Color(0xffdc3545),
|
||||||
this.onDanger = const Color(0xffffffff),
|
this.onDanger = Colors.white,
|
||||||
this.warning = const Color(0xffffc107),
|
this.warning = const Color(0xffffc107),
|
||||||
this.onWarning = const Color(0xff313a46),
|
this.onWarning = const Color(0xff313a46),
|
||||||
this.info = const Color(0xff0dcaf0),
|
this.info = const Color(0xff0dcaf0),
|
||||||
this.onInfo = const Color(0xffffffff),
|
this.onInfo = Colors.white,
|
||||||
this.light = const Color(0xffeef2f7),
|
this.light = const Color(0xffeef2f7),
|
||||||
this.onLight = const Color(0xff313a46),
|
this.onLight = const Color(0xff313a46),
|
||||||
this.dark = const Color(0xff313a46),
|
this.dark = const Color(0xff313a46),
|
||||||
this.onDark = const Color(0xffffffff),
|
this.onDark = Colors.white,
|
||||||
this.purple = const Color(0xff800080),
|
|
||||||
this.onPurple = const Color(0xffFF0000),
|
|
||||||
this.pink = const Color(0xffFF1087),
|
this.pink = const Color(0xffFF1087),
|
||||||
this.onPink = const Color(0xffffffff),
|
this.onPink = Colors.white,
|
||||||
this.red = const Color(0xffFF0000),
|
this.red = const Color(0xffFF0000),
|
||||||
this.onRed = const Color(0xffffffff),
|
this.onRed = Colors.white,
|
||||||
this.brandRed = const Color.fromARGB(255, 255, 0, 0),
|
this.brandColor = const Color(0xffff0000),
|
||||||
this.onBrandRed = const Color(0xffffffff),
|
this.onBrandColor = Colors.white,
|
||||||
|
this.buttonColor = const Color(0xFF2196F3),
|
||||||
|
this.onButtonColor = Colors.white,
|
||||||
this.cardBackground = const Color(0xffffffff),
|
this.cardBackground = const Color(0xffffffff),
|
||||||
this.cardShadow = const Color(0xffffffff),
|
this.cardShadow = const Color(0xffffffff),
|
||||||
this.cardBorder = const Color(0xffffffff),
|
this.cardBorder = const Color(0xffffffff),
|
||||||
@ -180,10 +191,21 @@ class ContentTheme {
|
|||||||
this.onDisabled = const Color(0xffffffff),
|
this.onDisabled = const Color(0xffffffff),
|
||||||
});
|
});
|
||||||
|
|
||||||
//-------------------------------------- Left Bar Theme ----------------------------------------//
|
// copyWith for dynamic updates
|
||||||
|
ContentTheme copyWith({
|
||||||
|
Color? primary,
|
||||||
|
Color? brandColor,
|
||||||
|
Color? buttonColor,
|
||||||
|
}) {
|
||||||
|
return ContentTheme(
|
||||||
|
primary: primary ?? this.primary,
|
||||||
|
brandColor: brandColor ?? this.brandColor,
|
||||||
|
buttonColor: buttonColor ?? this.buttonColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static final ContentTheme lightContentTheme = ContentTheme(
|
static final ContentTheme lightContentTheme = ContentTheme(
|
||||||
primary: Color(0xff663399),
|
primary: Colors.indigo,
|
||||||
background: const Color(0xfffafbfe),
|
background: const Color(0xfffafbfe),
|
||||||
onBackground: const Color(0xff313a46),
|
onBackground: const Color(0xff313a46),
|
||||||
cardBorder: const Color(0xffe8ecf1),
|
cardBorder: const Color(0xffe8ecf1),
|
||||||
@ -192,12 +214,12 @@ class ContentTheme {
|
|||||||
cardText: const Color(0xff6c757d),
|
cardText: const Color(0xff6c757d),
|
||||||
title: const Color(0xff6c757d),
|
title: const Color(0xff6c757d),
|
||||||
cardTextMuted: const Color(0xff98a6ad),
|
cardTextMuted: const Color(0xff98a6ad),
|
||||||
brandRed: const Color.fromARGB(255, 255, 0, 0),
|
brandColor: const Color(0xffff0000),
|
||||||
onBrandRed: const Color(0xffffffff),
|
onBrandColor: Colors.white,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final ContentTheme darkContentTheme = ContentTheme(
|
static final ContentTheme darkContentTheme = ContentTheme(
|
||||||
primary: Color(0xff32BFAE),
|
primary: Colors.indigo,
|
||||||
background: const Color(0xff343a40),
|
background: const Color(0xff343a40),
|
||||||
onBackground: const Color(0xffF1F1F2),
|
onBackground: const Color(0xffF1F1F2),
|
||||||
disabled: const Color(0xff444d57),
|
disabled: const Color(0xff444d57),
|
||||||
@ -208,8 +230,8 @@ class ContentTheme {
|
|||||||
cardText: const Color(0xffaab8c5),
|
cardText: const Color(0xffaab8c5),
|
||||||
title: const Color(0xffaab8c5),
|
title: const Color(0xffaab8c5),
|
||||||
cardTextMuted: const Color(0xff8391a2),
|
cardTextMuted: const Color(0xff8391a2),
|
||||||
brandRed: const Color.fromARGB(255, 255, 0, 0),
|
brandColor: const Color(0xffff0000),
|
||||||
onBrandRed: const Color(0xffffffff),
|
onBrandColor: Colors.white,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,8 +248,6 @@ class AdminTheme {
|
|||||||
required this.contentTheme,
|
required this.contentTheme,
|
||||||
});
|
});
|
||||||
|
|
||||||
//-------------------------------------- Left Bar Theme ----------------------------------------//
|
|
||||||
|
|
||||||
static AdminTheme theme = AdminTheme(
|
static AdminTheme theme = AdminTheme(
|
||||||
leftBarTheme: LeftBarTheme.lightLeftBarTheme,
|
leftBarTheme: LeftBarTheme.lightLeftBarTheme,
|
||||||
topBarTheme: TopBarTheme.lightTopBarTheme,
|
topBarTheme: TopBarTheme.lightTopBarTheme,
|
||||||
@ -236,9 +256,35 @@ class AdminTheme {
|
|||||||
|
|
||||||
static void setTheme() {
|
static void setTheme() {
|
||||||
theme = AdminTheme(
|
theme = AdminTheme(
|
||||||
leftBarTheme: ThemeCustomizer.instance.theme == ThemeMode.dark ? LeftBarTheme.darkLeftBarTheme : LeftBarTheme.lightLeftBarTheme,
|
leftBarTheme: ThemeCustomizer.instance.theme == ThemeMode.dark
|
||||||
topBarTheme: ThemeCustomizer.instance.theme == ThemeMode.dark ? TopBarTheme.darkTopBarTheme : TopBarTheme.lightTopBarTheme,
|
? LeftBarTheme.darkLeftBarTheme
|
||||||
rightBarTheme: ThemeCustomizer.instance.theme == ThemeMode.dark ? RightBarTheme.darkRightBarTheme : RightBarTheme.lightRightBarTheme,
|
: LeftBarTheme.lightLeftBarTheme,
|
||||||
contentTheme: ThemeCustomizer.instance.theme == ThemeMode.dark ? ContentTheme.darkContentTheme : ContentTheme.lightContentTheme);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic theme color update
|
||||||
|
static void updateThemeColors({
|
||||||
|
Color? primary,
|
||||||
|
Color? brandColor,
|
||||||
|
Color? buttonColor,
|
||||||
|
}) {
|
||||||
|
theme = AdminTheme(
|
||||||
|
leftBarTheme: theme.leftBarTheme,
|
||||||
|
topBarTheme: theme.topBarTheme,
|
||||||
|
rightBarTheme: theme.rightBarTheme,
|
||||||
|
contentTheme: theme.contentTheme.copyWith(
|
||||||
|
primary: primary,
|
||||||
|
brandColor: brandColor,
|
||||||
|
buttonColor: buttonColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
216
lib/helpers/theme/theme_editor_widget.dart
Normal file
216
lib/helpers/theme/theme_editor_widget.dart
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class ThemeOption {
|
||||||
|
final String label;
|
||||||
|
final Color primary;
|
||||||
|
final Color button;
|
||||||
|
final Color brand;
|
||||||
|
|
||||||
|
ThemeOption(this.label, this.primary, this.button, this.brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define your themes
|
||||||
|
final List<ThemeOption> themeOptions = [
|
||||||
|
ThemeOption("Theme 1", Colors.red, Colors.red, Colors.red),
|
||||||
|
ThemeOption("Theme 2", const Color(0xFF49BF3C), const Color(0xFF49BF3C), const Color(0xFF49BF3C)),
|
||||||
|
ThemeOption("Theme 3", const Color(0xFF3F51B5), const Color(0xFF3F51B5), const Color(0xFF3F51B5)),
|
||||||
|
ThemeOption("Theme 4", const Color(0xFF40B7FF), const Color(0xFF40B7FF), const Color(0xFF40B7FF)),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Reactive controller
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Update AdminTheme
|
||||||
|
AdminTheme.updateThemeColors(
|
||||||
|
primary: themeOptions[index].primary,
|
||||||
|
buttonColor: themeOptions[index].button,
|
||||||
|
brandColor: themeOptions[index].brand,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(milliseconds: 600));
|
||||||
|
showApplied.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theme Editor Widget
|
||||||
|
class ThemeEditorWidget extends StatelessWidget {
|
||||||
|
final VoidCallback onClose;
|
||||||
|
ThemeEditorWidget({Key? key, required this.onClose}) : super(key: key);
|
||||||
|
|
||||||
|
final ThemeController themeController = Get.put(ThemeController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
themeController.init();
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
MyText.bodyLarge("Theme Customization", fontWeight: 600),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: onClose,
|
||||||
|
tooltip: "Back",
|
||||||
|
iconSize: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Theme Cards
|
||||||
|
Center(
|
||||||
|
child: Obx(() => Wrap(
|
||||||
|
spacing: 12,
|
||||||
|
runSpacing: 12,
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
children: List.generate(themeOptions.length, (i) {
|
||||||
|
final theme = themeOptions[i];
|
||||||
|
final isSelected = themeController.selectedIndex.value == i;
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: 80,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => themeController.applyTheme(i),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? theme.brand : Colors.transparent,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 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(theme.brand, 0.15),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Hello, User!",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: theme.primary,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
SizedBox(
|
||||||
|
height: 18,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: theme.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(
|
||||||
|
theme.label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Applied Indicator
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:marco/helpers/widgets/my_spacing.dart';
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||||
import 'package:marco/helpers/widgets/my_text.dart';
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
|
||||||
class BaseBottomSheet extends StatelessWidget {
|
class BaseBottomSheet extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
|
final String? subtitle;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final VoidCallback onCancel;
|
final VoidCallback onCancel;
|
||||||
final VoidCallback onSubmit;
|
final VoidCallback onSubmit;
|
||||||
final bool isSubmitting;
|
final bool isSubmitting;
|
||||||
final String submitText;
|
final String submitText;
|
||||||
final Color submitColor;
|
final Color? submitColor;
|
||||||
final IconData submitIcon;
|
final IconData submitIcon;
|
||||||
final bool showButtons;
|
final bool showButtons;
|
||||||
final Widget? bottomContent;
|
final Widget? bottomContent;
|
||||||
@ -20,18 +22,26 @@ class BaseBottomSheet extends StatelessWidget {
|
|||||||
required this.child,
|
required this.child,
|
||||||
required this.onCancel,
|
required this.onCancel,
|
||||||
required this.onSubmit,
|
required this.onSubmit,
|
||||||
|
this.subtitle,
|
||||||
this.isSubmitting = false,
|
this.isSubmitting = false,
|
||||||
this.submitText = 'Submit',
|
this.submitText = 'Submit',
|
||||||
this.submitColor = Colors.indigo,
|
this.submitColor,
|
||||||
this.submitIcon = Icons.check_circle_outline,
|
this.submitIcon = Icons.check_circle_outline,
|
||||||
this.showButtons = true,
|
this.showButtons = true,
|
||||||
this.bottomContent,
|
this.bottomContent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BaseBottomSheet> createState() => _BaseBottomSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BaseBottomSheetState extends State<BaseBottomSheet> with UIMixin {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
final effectiveSubmitColor =
|
||||||
|
widget.submitColor ?? contentTheme.primary;
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: mediaQuery.viewInsets,
|
padding: mediaQuery.viewInsets,
|
||||||
@ -50,33 +60,50 @@ class BaseBottomSheet extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
// 👈 prevents overlap with nav bar
|
|
||||||
top: false,
|
top: false,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 32),
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 32),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
MySpacing.height(5),
|
MySpacing.height(5),
|
||||||
Container(
|
Center(
|
||||||
width: 40,
|
child: Container(
|
||||||
height: 5,
|
width: 40,
|
||||||
decoration: BoxDecoration(
|
height: 5,
|
||||||
color: Colors.grey.shade300,
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(10),
|
color: Colors.grey.shade300,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
MyText.titleLarge(title, fontWeight: 700),
|
Center(
|
||||||
|
child: MyText.titleLarge(
|
||||||
|
widget.title,
|
||||||
|
fontWeight: 700,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.subtitle != null &&
|
||||||
|
widget.subtitle!.isNotEmpty) ...[
|
||||||
|
MySpacing.height(4),
|
||||||
|
MyText.bodySmall(
|
||||||
|
widget.subtitle!,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
|
],
|
||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
child,
|
widget.child,
|
||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
if (showButtons) ...[
|
if (widget.showButtons) ...[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: onCancel,
|
onPressed: widget.onCancel,
|
||||||
icon: const Icon(Icons.close, color: Colors.white),
|
icon: const Icon(Icons.close, color: Colors.white),
|
||||||
label: MyText.bodyMedium(
|
label: MyText.bodyMedium(
|
||||||
"Cancel",
|
"Cancel",
|
||||||
@ -88,34 +115,40 @@ class BaseBottomSheet extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: isSubmitting ? null : onSubmit,
|
onPressed:
|
||||||
icon: Icon(submitIcon, color: Colors.white),
|
widget.isSubmitting ? null : widget.onSubmit,
|
||||||
|
icon:
|
||||||
|
Icon(widget.submitIcon, color: Colors.white),
|
||||||
label: MyText.bodyMedium(
|
label: MyText.bodyMedium(
|
||||||
isSubmitting ? "Submitting..." : submitText,
|
widget.isSubmitting
|
||||||
|
? "Submitting..."
|
||||||
|
: widget.submitText,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
),
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: submitColor,
|
backgroundColor: effectiveSubmitColor,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (bottomContent != null) ...[
|
if (widget.bottomContent != null) ...[
|
||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
bottomContent!,
|
widget.bottomContent!,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
69
lib/helpers/widgets/wave_background.dart
Normal file
69
lib/helpers/widgets/wave_background.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RedWaveBackground extends StatelessWidget {
|
||||||
|
final Color brandRed;
|
||||||
|
final double heightFactor;
|
||||||
|
|
||||||
|
const RedWaveBackground({
|
||||||
|
super.key,
|
||||||
|
required this.brandRed,
|
||||||
|
this.heightFactor = 0.2,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: RedWavePainter(brandRed, heightFactor),
|
||||||
|
size: Size.infinite,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RedWavePainter extends CustomPainter {
|
||||||
|
final Color brandRed;
|
||||||
|
final double heightFactor;
|
||||||
|
|
||||||
|
RedWavePainter(this.brandRed, this.heightFactor);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint1 = Paint()
|
||||||
|
..shader = LinearGradient(
|
||||||
|
colors: [brandRed, const Color.fromARGB(255, 97, 22, 22)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
..quadraticBezierTo(
|
||||||
|
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);
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
..lineTo(size.width, 0)
|
||||||
|
..lineTo(0, 0)
|
||||||
|
..close();
|
||||||
|
|
||||||
|
canvas.drawPath(path2, paint2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
@ -8,8 +8,9 @@ import 'package:marco/helpers/widgets/my_text.dart';
|
|||||||
import 'package:marco/helpers/widgets/my_text_style.dart';
|
import 'package:marco/helpers/widgets/my_text_style.dart';
|
||||||
import 'package:marco/model/document/document_filter_model.dart';
|
import 'package:marco/model/document/document_filter_model.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
|
||||||
class UserDocumentFilterBottomSheet extends StatelessWidget {
|
class UserDocumentFilterBottomSheet extends StatelessWidget with UIMixin {
|
||||||
final String entityId;
|
final String entityId;
|
||||||
final String entityTypeId;
|
final String entityTypeId;
|
||||||
final DocumentController docController = Get.find<DocumentController>();
|
final DocumentController docController = Get.find<DocumentController>();
|
||||||
@ -100,7 +101,7 @@ class UserDocumentFilterBottomSheet extends StatelessWidget {
|
|||||||
vertical: 10),
|
vertical: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: docController.isUploadedAt.value
|
color: docController.isUploadedAt.value
|
||||||
? Colors.indigo.shade400
|
? contentTheme.buttonColor
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
borderRadius:
|
borderRadius:
|
||||||
const BorderRadius.horizontal(
|
const BorderRadius.horizontal(
|
||||||
@ -131,7 +132,7 @@ class UserDocumentFilterBottomSheet extends StatelessWidget {
|
|||||||
vertical: 10),
|
vertical: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: !docController.isUploadedAt.value
|
color: !docController.isUploadedAt.value
|
||||||
? Colors.indigo.shade400
|
? contentTheme.buttonColor
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
borderRadius:
|
borderRadius:
|
||||||
const BorderRadius.horizontal(
|
const BorderRadius.horizontal(
|
||||||
@ -264,7 +265,7 @@ class UserDocumentFilterBottomSheet extends StatelessWidget {
|
|||||||
onChanged: (val) =>
|
onChanged: (val) =>
|
||||||
docController.isVerified.value = val,
|
docController.isVerified.value = val,
|
||||||
activeColor:
|
activeColor:
|
||||||
Colors.indigo,
|
contentTheme.buttonColor,
|
||||||
materialTapTargetSize:
|
materialTapTargetSize:
|
||||||
MaterialTapTargetSize.shrinkWrap,
|
MaterialTapTargetSize.shrinkWrap,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -94,7 +94,7 @@ class _EmailLoginFormState extends State<EmailLoginForm> with UIMixin {
|
|||||||
MaterialStateProperty.resolveWith<Color>(
|
MaterialStateProperty.resolveWith<Color>(
|
||||||
(states) =>
|
(states) =>
|
||||||
states.contains(WidgetState.selected)
|
states.contains(WidgetState.selected)
|
||||||
? contentTheme.brandRed
|
? contentTheme.brandColor
|
||||||
: Colors.white,
|
: Colors.white,
|
||||||
),
|
),
|
||||||
checkColor: contentTheme.onPrimary,
|
checkColor: contentTheme.onPrimary,
|
||||||
@ -132,7 +132,7 @@ class _EmailLoginFormState extends State<EmailLoginForm> with UIMixin {
|
|||||||
elevation: 2,
|
elevation: 2,
|
||||||
padding: MySpacing.xy(80, 16),
|
padding: MySpacing.xy(80, 16),
|
||||||
borderRadiusAll: 10,
|
borderRadiusAll: 10,
|
||||||
backgroundColor: contentTheme.brandRed,
|
backgroundColor: contentTheme.brandColor,
|
||||||
child: MyText.labelLarge(
|
child: MyText.labelLarge(
|
||||||
isLoading ? 'Logging in...' : 'Login',
|
isLoading ? 'Logging in...' : 'Login',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
|||||||
import 'package:marco/helpers/widgets/my_button.dart';
|
import 'package:marco/helpers/widgets/my_button.dart';
|
||||||
import 'package:marco/helpers/widgets/my_text.dart';
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
import 'package:marco/images.dart';
|
import 'package:marco/images.dart';
|
||||||
|
import 'package:marco/helpers/widgets/wave_background.dart';
|
||||||
|
|
||||||
class ForgotPasswordScreen extends StatefulWidget {
|
class ForgotPasswordScreen extends StatefulWidget {
|
||||||
const ForgotPasswordScreen({super.key});
|
const ForgotPasswordScreen({super.key});
|
||||||
@ -59,7 +60,7 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen>
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
_RedWaveBackground(brandRed: contentTheme.brandRed),
|
RedWaveBackground(brandRed: contentTheme.brandColor),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -230,8 +231,8 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen>
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16),
|
||||||
borderRadiusAll: 10,
|
borderRadiusAll: 10,
|
||||||
backgroundColor: _isLoading
|
backgroundColor: _isLoading
|
||||||
? contentTheme.brandRed.withOpacity(0.6)
|
? contentTheme.brandColor.withOpacity(0.6)
|
||||||
: contentTheme.brandRed,
|
: contentTheme.brandColor,
|
||||||
child: _isLoading
|
child: _isLoading
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
@ -253,68 +254,13 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen>
|
|||||||
Widget _buildBackButton() {
|
Widget _buildBackButton() {
|
||||||
return TextButton.icon(
|
return TextButton.icon(
|
||||||
onPressed: () async => await LocalStorage.logout(),
|
onPressed: () async => await LocalStorage.logout(),
|
||||||
icon: const Icon(Icons.arrow_back, size: 18, color: Colors.redAccent),
|
icon: Icon(Icons.arrow_back, size: 18, color: contentTheme.brandColor,),
|
||||||
label: MyText.bodyMedium(
|
label: MyText.bodyMedium(
|
||||||
'Back to Login',
|
'Back to Login',
|
||||||
color: contentTheme.brandRed,
|
color: contentTheme.brandColor,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RedWaveBackground extends StatelessWidget {
|
|
||||||
final Color brandRed;
|
|
||||||
const _RedWaveBackground({required this.brandRed});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return CustomPaint(
|
|
||||||
painter: _WavePainter(brandRed),
|
|
||||||
size: Size.infinite,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WavePainter extends CustomPainter {
|
|
||||||
final Color brandRed;
|
|
||||||
|
|
||||||
_WavePainter(this.brandRed);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final paint1 = Paint()
|
|
||||||
..shader = LinearGradient(
|
|
||||||
colors: [brandRed, const Color.fromARGB(255, 97, 22, 22)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
|
|
||||||
|
|
||||||
final path1 = Path()
|
|
||||||
..moveTo(0, size.height * 0.2)
|
|
||||||
..quadraticBezierTo(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)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
|
|
||||||
canvas.drawPath(path1, paint1);
|
|
||||||
|
|
||||||
final paint2 = Paint()..color = Colors.redAccent.withOpacity(0.15);
|
|
||||||
final path2 = Path()
|
|
||||||
..moveTo(0, size.height * 0.25)
|
|
||||||
..quadraticBezierTo(
|
|
||||||
size.width * 0.4, size.height * 0.1, size.width, size.height * 0.2)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
|
|
||||||
canvas.drawPath(path2, paint2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'package:marco/view/auth/otp_login_form.dart';
|
|||||||
import 'package:marco/helpers/services/api_endpoints.dart';
|
import 'package:marco/helpers/services/api_endpoints.dart';
|
||||||
import 'package:marco/view/auth/request_demo_bottom_sheet.dart';
|
import 'package:marco/view/auth/request_demo_bottom_sheet.dart';
|
||||||
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
import 'package:marco/helpers/widgets/wave_background.dart';
|
||||||
|
|
||||||
enum LoginOption { email, otp }
|
enum LoginOption { email, otp }
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ class _WelcomeScreenState extends State<WelcomeScreen>
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
option == LoginOption.email
|
option == LoginOption.email
|
||||||
? EmailLoginForm()
|
? EmailLoginForm()
|
||||||
: const OTPLoginScreen(),
|
: const OTPLoginScreen(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -101,13 +102,14 @@ class _WelcomeScreenState extends State<WelcomeScreen>
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
_RedWaveBackground(brandRed: contentTheme.brandRed),
|
RedWaveBackground(brandRed: contentTheme.brandColor),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: isNarrow ? double.infinity : 420),
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: isNarrow ? double.infinity : 420),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -166,7 +168,10 @@ class _WelcomeScreenState extends State<WelcomeScreen>
|
|||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10, offset: Offset(0, 4))],
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black12, blurRadius: 10, offset: Offset(0, 4))
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: Image.asset(Images.logoDark),
|
child: Image.asset(Images.logoDark),
|
||||||
),
|
),
|
||||||
@ -230,9 +235,10 @@ class _WelcomeScreenState extends State<WelcomeScreen>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: contentTheme.brandRed,
|
backgroundColor: contentTheme.brandColor,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
shape:
|
||||||
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
shadowColor: Colors.black26,
|
shadowColor: Colors.black26,
|
||||||
),
|
),
|
||||||
@ -247,55 +253,3 @@ class _WelcomeScreenState extends State<WelcomeScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Red wave background painter
|
|
||||||
class _RedWaveBackground extends StatelessWidget {
|
|
||||||
final Color brandRed;
|
|
||||||
const _RedWaveBackground({required this.brandRed});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return CustomPaint(
|
|
||||||
painter: _WavePainter(brandRed),
|
|
||||||
size: Size.infinite,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WavePainter extends CustomPainter {
|
|
||||||
final Color brandRed;
|
|
||||||
_WavePainter(this.brandRed);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final paint1 = Paint()
|
|
||||||
..shader = LinearGradient(
|
|
||||||
colors: [brandRed, const Color.fromARGB(255, 97, 22, 22)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
|
|
||||||
|
|
||||||
final path1 = Path()
|
|
||||||
..moveTo(0, size.height * 0.2)
|
|
||||||
..quadraticBezierTo(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)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
|
|
||||||
canvas.drawPath(path1, paint1);
|
|
||||||
|
|
||||||
final paint2 = Paint()..color = Colors.redAccent.withOpacity(0.15);
|
|
||||||
final path2 = Path()
|
|
||||||
..moveTo(0, size.height * 0.25)
|
|
||||||
..quadraticBezierTo(size.width * 0.4, size.height * 0.1, size.width, size.height * 0.2)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
|
|
||||||
canvas.drawPath(path2, paint2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import 'package:marco/images.dart';
|
|||||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||||
import 'package:marco/helpers/services/api_endpoints.dart';
|
import 'package:marco/helpers/services/api_endpoints.dart';
|
||||||
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
import 'package:marco/helpers/widgets/wave_background.dart';
|
||||||
|
|
||||||
|
|
||||||
class MPINAuthScreen extends StatefulWidget {
|
class MPINAuthScreen extends StatefulWidget {
|
||||||
const MPINAuthScreen({super.key});
|
const MPINAuthScreen({super.key});
|
||||||
@ -51,7 +53,7 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
_RedWaveBackground(brandRed: contentTheme.brandRed),
|
RedWaveBackground(brandRed: contentTheme.brandColor),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -281,8 +283,8 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||||
borderRadiusAll: 10,
|
borderRadiusAll: 10,
|
||||||
backgroundColor: controller.isLoading.value
|
backgroundColor: controller.isLoading.value
|
||||||
? contentTheme.brandRed.withOpacity(0.6)
|
? contentTheme.brandColor.withOpacity(0.6)
|
||||||
: contentTheme.brandRed,
|
: contentTheme.brandColor,
|
||||||
child: controller.isLoading.value
|
child: controller.isLoading.value
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
@ -315,11 +317,11 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
|
|||||||
if (isNewUser || isChangeMpin)
|
if (isNewUser || isChangeMpin)
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => Get.toNamed('/dashboard'),
|
onPressed: () => Get.toNamed('/dashboard'),
|
||||||
icon: const Icon(Icons.arrow_back,
|
icon: Icon(Icons.arrow_back,
|
||||||
size: 18, color: Colors.redAccent),
|
size: 18, color: contentTheme.brandColor),
|
||||||
label: MyText.bodyMedium(
|
label: MyText.bodyMedium(
|
||||||
'Back to Home Page',
|
'Back to Home Page',
|
||||||
color: contentTheme.brandRed,
|
color: contentTheme.brandColor,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
@ -331,7 +333,7 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
|
|||||||
size: 18, color: Colors.redAccent),
|
size: 18, color: Colors.redAccent),
|
||||||
label: MyText.bodyMedium(
|
label: MyText.bodyMedium(
|
||||||
'Go back to Login Screen',
|
'Go back to Login Screen',
|
||||||
color: contentTheme.brandRed,
|
color: contentTheme.brandColor,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
@ -341,57 +343,3 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RedWaveBackground extends StatelessWidget {
|
|
||||||
final Color brandRed;
|
|
||||||
const _RedWaveBackground({required this.brandRed});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return CustomPaint(
|
|
||||||
painter: _WavePainter(brandRed),
|
|
||||||
size: Size.infinite,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WavePainter extends CustomPainter {
|
|
||||||
final Color brandRed;
|
|
||||||
const _WavePainter(this.brandRed);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final paint1 = Paint()
|
|
||||||
..shader = LinearGradient(
|
|
||||||
colors: [brandRed, const Color.fromARGB(255, 97, 22, 22)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
|
|
||||||
|
|
||||||
final path1 = Path()
|
|
||||||
..moveTo(0, size.height * 0.2)
|
|
||||||
..quadraticBezierTo(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)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
|
|
||||||
canvas.drawPath(path1, paint1);
|
|
||||||
|
|
||||||
final paint2 = Paint()..color = Colors.redAccent.withOpacity(0.15);
|
|
||||||
final path2 = Path()
|
|
||||||
..moveTo(0, size.height * 0.25)
|
|
||||||
..quadraticBezierTo(
|
|
||||||
size.width * 0.4, size.height * 0.1, size.width, size.height * 0.2)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
|
|
||||||
canvas.drawPath(path2, paint2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -81,7 +81,7 @@ class _OTPLoginScreenState extends State<OTPLoginScreen> with UIMixin {
|
|||||||
elevation: 2,
|
elevation: 2,
|
||||||
padding: MySpacing.xy(24, 16),
|
padding: MySpacing.xy(24, 16),
|
||||||
borderRadiusAll: 10,
|
borderRadiusAll: 10,
|
||||||
backgroundColor: isDisabled ? Colors.grey : contentTheme.brandRed,
|
backgroundColor: isDisabled ? Colors.grey : contentTheme.brandColor,
|
||||||
child: controller.isSending.value
|
child: controller.isSending.value
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
width: 20,
|
width: 20,
|
||||||
@ -170,7 +170,7 @@ class _OTPLoginScreenState extends State<OTPLoginScreen> with UIMixin {
|
|||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide(color: contentTheme.brandRed, width: 2),
|
borderSide: BorderSide(color: contentTheme.brandColor, width: 2),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -220,7 +220,7 @@ class _OTPLoginScreenState extends State<OTPLoginScreen> with UIMixin {
|
|||||||
elevation: 2,
|
elevation: 2,
|
||||||
padding: MySpacing.xy(24, 16),
|
padding: MySpacing.xy(24, 16),
|
||||||
borderRadiusAll: 10,
|
borderRadiusAll: 10,
|
||||||
backgroundColor: contentTheme.brandRed,
|
backgroundColor: contentTheme.brandColor,
|
||||||
child: MyText.labelMedium(
|
child: MyText.labelMedium(
|
||||||
'Verify OTP',
|
'Verify OTP',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:marco/helpers/services/auth_service.dart';
|
|||||||
import 'package:marco/helpers/widgets/my_snackbar.dart';
|
import 'package:marco/helpers/widgets/my_snackbar.dart';
|
||||||
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
import 'package:marco/helpers/widgets/my_text.dart';
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||||
|
|
||||||
class OrganizationFormBottomSheet {
|
class OrganizationFormBottomSheet {
|
||||||
static void show(BuildContext context) {
|
static void show(BuildContext context) {
|
||||||
@ -81,170 +82,203 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return SingleChildScrollView(
|
||||||
decoration: const BoxDecoration(
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
color: Colors.white,
|
child: Padding(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
padding: const EdgeInsets.only(top: 60),
|
||||||
),
|
child: Container(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 40),
|
decoration: BoxDecoration(
|
||||||
child: SingleChildScrollView(
|
color: Colors.white,
|
||||||
controller: widget.scrollController,
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
||||||
child: Form(
|
boxShadow: const [
|
||||||
key: validator.formKey,
|
BoxShadow(
|
||||||
child: Column(
|
color: Colors.black12,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
blurRadius: 12,
|
||||||
children: [
|
offset: Offset(0, -2),
|
||||||
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.brandRed
|
|
||||||
: Colors.white),
|
|
||||||
checkColor: Colors.white,
|
|
||||||
side: const BorderSide(color: Colors.red, width: 2),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
MyText(
|
|
||||||
'I agree to the ',
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
MyText(
|
|
||||||
'privacy policy & terms',
|
|
||||||
color: contentTheme.brandRed,
|
|
||||||
fontWeight: 600,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
icon: const Icon(Icons.arrow_back, color: Colors.red),
|
|
||||||
label: MyText.bodyMedium("Back", color: Colors.red),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
side: const BorderSide(color: Colors.red),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 20, vertical: 14),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ElevatedButton.icon(
|
|
||||||
onPressed: _loading ? null : _submitForm,
|
|
||||||
icon: _loading
|
|
||||||
? const SizedBox(
|
|
||||||
width: 18,
|
|
||||||
height: 18,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
color: Colors.white,
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const Icon(Icons.check_circle_outline,
|
|
||||||
color: Colors.white),
|
|
||||||
label: _loading
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: MyText.bodyMedium("Submit", color: Colors.white),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.indigo,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 28, vertical: 14),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Center(
|
|
||||||
child: TextButton.icon(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
icon:
|
|
||||||
const Icon(Icons.arrow_back, size: 18, color: Colors.red),
|
|
||||||
label: MyText.bodySmall(
|
|
||||||
'Back to log in',
|
|
||||||
fontWeight: 600,
|
|
||||||
color: contentTheme.brandRed,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
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.brandColor
|
||||||
|
: Colors.white),
|
||||||
|
checkColor: Colors.white,
|
||||||
|
side: BorderSide(color: contentTheme.brandColor, width: 2),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Wrap(
|
||||||
|
children: [
|
||||||
|
MyText(
|
||||||
|
'I agree to the ',
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
MyText(
|
||||||
|
'privacy policy & terms',
|
||||||
|
color: contentTheme.brandColor,
|
||||||
|
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.buttonColor,
|
||||||
|
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.brandColor,
|
||||||
|
),
|
||||||
|
label: MyText.bodySmall(
|
||||||
|
'Back to log in',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: contentTheme.brandColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -300,7 +334,7 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin {
|
|||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide(color: contentTheme.brandRed, width: 1.5),
|
borderSide: BorderSide(color: contentTheme.brandColor, width: 1.5),
|
||||||
),
|
),
|
||||||
errorBorder: OutlineInputBorder(
|
errorBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@ -377,7 +411,7 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin {
|
|||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide:
|
borderSide:
|
||||||
BorderSide(color: contentTheme.brandRed, width: 1.5),
|
BorderSide(color: contentTheme.brandColor, width: 1.5),
|
||||||
),
|
),
|
||||||
errorText: fieldState.errorText,
|
errorText: fieldState.errorText,
|
||||||
),
|
),
|
||||||
@ -386,8 +420,7 @@ class _OrganizationFormState extends State<_OrganizationForm> with UIMixin {
|
|||||||
children: [
|
children: [
|
||||||
MyText.bodyMedium(
|
MyText.bodyMedium(
|
||||||
selectedValue ?? 'Select $label',
|
selectedValue ?? 'Select $label',
|
||||||
color:
|
color: selectedValue == null ? Colors.grey : Colors.black,
|
||||||
selectedValue == null ? Colors.grey : Colors.black,
|
|
||||||
),
|
),
|
||||||
const Icon(Icons.arrow_drop_down, color: Colors.grey),
|
const Icon(Icons.arrow_drop_down, color: Colors.grey),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
|
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
import 'package:marco/controller/directory/directory_controller.dart';
|
import 'package:marco/controller/directory/directory_controller.dart';
|
||||||
import 'package:marco/controller/directory/create_bucket_controller.dart';
|
import 'package:marco/controller/directory/create_bucket_controller.dart';
|
||||||
import 'package:marco/helpers/utils/launcher_utils.dart';
|
import 'package:marco/helpers/utils/launcher_utils.dart';
|
||||||
@ -24,7 +24,7 @@ class DirectoryView extends StatefulWidget {
|
|||||||
State<DirectoryView> createState() => _DirectoryViewState();
|
State<DirectoryView> createState() => _DirectoryViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DirectoryViewState extends State<DirectoryView> {
|
class _DirectoryViewState extends State<DirectoryView> with UIMixin {
|
||||||
final DirectoryController controller = Get.find();
|
final DirectoryController controller = Get.find();
|
||||||
final TextEditingController searchController = TextEditingController();
|
final TextEditingController searchController = TextEditingController();
|
||||||
final PermissionController permissionController =
|
final PermissionController permissionController =
|
||||||
@ -127,7 +127,7 @@ class _DirectoryViewState extends State<DirectoryView> {
|
|||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () => Navigator.pop(context, true),
|
onPressed: () => Navigator.pop(context, true),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.indigo,
|
backgroundColor: contentTheme.buttonColor,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
@ -173,7 +173,7 @@ class _DirectoryViewState extends State<DirectoryView> {
|
|||||||
backgroundColor: Colors.grey[100],
|
backgroundColor: Colors.grey[100],
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
heroTag: 'createContact',
|
heroTag: 'createContact',
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: contentTheme.buttonColor,
|
||||||
onPressed: _handleCreateContact,
|
onPressed: _handleCreateContact,
|
||||||
icon: const Icon(Icons.person_add_alt_1, color: Colors.white),
|
icon: const Icon(Icons.person_add_alt_1, color: Colors.white),
|
||||||
label: const Text("Add Contact", style: TextStyle(color: Colors.white)),
|
label: const Text("Add Contact", style: TextStyle(color: Colors.white)),
|
||||||
@ -319,13 +319,13 @@ class _DirectoryViewState extends State<DirectoryView> {
|
|||||||
PopupMenuItem<int>(
|
PopupMenuItem<int>(
|
||||||
value: 2,
|
value: 2,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: const [
|
children: [
|
||||||
Icon(Icons.add_box_outlined,
|
Icon(Icons.add_box_outlined,
|
||||||
size: 20, color: Colors.black87),
|
size: 20, color: Colors.black87),
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 10),
|
||||||
Expanded(child: Text("Create Bucket")),
|
Expanded(child: Text("Create Bucket")),
|
||||||
Icon(Icons.chevron_right,
|
Icon(Icons.chevron_right,
|
||||||
size: 20, color: Colors.red),
|
size: 20, color: contentTheme.buttonColor),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -352,13 +352,13 @@ class _DirectoryViewState extends State<DirectoryView> {
|
|||||||
PopupMenuItem<int>(
|
PopupMenuItem<int>(
|
||||||
value: 1,
|
value: 1,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: const [
|
children: [
|
||||||
Icon(Icons.label_outline,
|
Icon(Icons.label_outline,
|
||||||
size: 20, color: Colors.black87),
|
size: 20, color: Colors.black87),
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 10),
|
||||||
Expanded(child: Text("Manage Buckets")),
|
Expanded(child: Text("Manage Buckets")),
|
||||||
Icon(Icons.chevron_right,
|
Icon(Icons.chevron_right,
|
||||||
size: 20, color: Colors.red),
|
size: 20, color: contentTheme.buttonColor),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -395,7 +395,7 @@ class _DirectoryViewState extends State<DirectoryView> {
|
|||||||
child: Text('Show Deleted Contacts')),
|
child: Text('Show Deleted Contacts')),
|
||||||
Switch.adaptive(
|
Switch.adaptive(
|
||||||
value: !controller.isActive.value,
|
value: !controller.isActive.value,
|
||||||
activeColor: Colors.indigo,
|
activeColor: contentTheme.buttonColor,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
controller.isActive.value = !val;
|
controller.isActive.value = !val;
|
||||||
controller.fetchContacts(active: !val);
|
controller.fetchContacts(active: !val);
|
||||||
|
|||||||
@ -134,8 +134,8 @@ class _NotesViewState extends State<NotesView> with UIMixin {
|
|||||||
),
|
),
|
||||||
if (note.isActive) ...[
|
if (note.isActive) ...[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(isEditing ? Icons.close : Icons.edit_outlined,
|
icon: Icon(isEditing ? Icons.close : Icons.edit,
|
||||||
color: Colors.indigo, size: 18),
|
color: contentTheme.buttonColor, size: 18),
|
||||||
splashRadius: 18,
|
splashRadius: 18,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
controller.editingNoteId.value =
|
controller.editingNoteId.value =
|
||||||
@ -143,7 +143,7 @@ class _NotesViewState extends State<NotesView> with UIMixin {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.delete_outline,
|
icon: const Icon(Icons.delete,
|
||||||
size: 18, color: Colors.red),
|
size: 18, color: Colors.red),
|
||||||
splashRadius: 18,
|
splashRadius: 18,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@ -224,7 +224,7 @@ class _NotesViewState extends State<NotesView> with UIMixin {
|
|||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
),
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: contentTheme.primary,
|
backgroundColor: contentTheme.buttonColor,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -19,6 +19,8 @@ import 'package:marco/model/document/document_upload_bottom_sheet.dart';
|
|||||||
import 'package:marco/model/document/documents_list_model.dart';
|
import 'package:marco/model/document/documents_list_model.dart';
|
||||||
import 'package:marco/model/document/user_document_filter_bottom_sheet.dart';
|
import 'package:marco/model/document/user_document_filter_bottom_sheet.dart';
|
||||||
import 'package:marco/view/document/document_details_page.dart';
|
import 'package:marco/view/document/document_details_page.dart';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
|
||||||
|
|
||||||
class UserDocumentsPage extends StatefulWidget {
|
class UserDocumentsPage extends StatefulWidget {
|
||||||
final String? entityId;
|
final String? entityId;
|
||||||
@ -34,7 +36,7 @@ class UserDocumentsPage extends StatefulWidget {
|
|||||||
State<UserDocumentsPage> createState() => _UserDocumentsPageState();
|
State<UserDocumentsPage> createState() => _UserDocumentsPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UserDocumentsPageState extends State<UserDocumentsPage> {
|
class _UserDocumentsPageState extends State<UserDocumentsPage> with UIMixin {
|
||||||
final DocumentController docController = Get.put(DocumentController());
|
final DocumentController docController = Get.put(DocumentController());
|
||||||
final PermissionController permissionController = Get.put(PermissionController());
|
final PermissionController permissionController = Get.put(PermissionController());
|
||||||
final DocumentDetailsController controller = Get.put(DocumentDetailsController());
|
final DocumentDetailsController controller = Get.put(DocumentDetailsController());
|
||||||
@ -395,7 +397,7 @@ class _UserDocumentsPageState extends State<UserDocumentsPage> {
|
|||||||
const Expanded(child: Text('Show Deleted Documents')),
|
const Expanded(child: Text('Show Deleted Documents')),
|
||||||
Switch.adaptive(
|
Switch.adaptive(
|
||||||
value: docController.showInactive.value,
|
value: docController.showInactive.value,
|
||||||
activeColor: Colors.indigo,
|
activeColor: contentTheme.buttonColor,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
docController.showInactive.value = val;
|
docController.showInactive.value = val;
|
||||||
docController.fetchDocuments(
|
docController.fetchDocuments(
|
||||||
@ -614,7 +616,7 @@ class _UserDocumentsPageState extends State<UserDocumentsPage> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: contentTheme.buttonColor,
|
||||||
)
|
)
|
||||||
: SizedBox.shrink();
|
: SizedBox.shrink();
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import 'package:marco/controller/permission_controller.dart';
|
|||||||
import 'package:marco/helpers/utils/permission_constants.dart';
|
import 'package:marco/helpers/utils/permission_constants.dart';
|
||||||
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
|
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
|
||||||
import 'package:marco/model/employees/add_employee_bottom_sheet.dart';
|
import 'package:marco/model/employees/add_employee_bottom_sheet.dart';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
|
||||||
|
|
||||||
class EmployeeDetailPage extends StatefulWidget {
|
class EmployeeDetailPage extends StatefulWidget {
|
||||||
final String employeeId;
|
final String employeeId;
|
||||||
@ -27,7 +29,7 @@ class EmployeeDetailPage extends StatefulWidget {
|
|||||||
State<EmployeeDetailPage> createState() => _EmployeeDetailPageState();
|
State<EmployeeDetailPage> createState() => _EmployeeDetailPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EmployeeDetailPageState extends State<EmployeeDetailPage> {
|
class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||||
final EmployeesScreenController controller =
|
final EmployeesScreenController controller =
|
||||||
Get.put(EmployeesScreenController());
|
Get.put(EmployeesScreenController());
|
||||||
final PermissionController permissionController =
|
final PermissionController permissionController =
|
||||||
@ -252,7 +254,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon:
|
icon:
|
||||||
const Icon(Icons.edit, size: 24, color: Colors.red),
|
Icon(Icons.edit, size: 24, color: contentTheme.buttonColor),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result =
|
final result =
|
||||||
await showModalBottomSheet<Map<String, dynamic>>(
|
await showModalBottomSheet<Map<String, dynamic>>(
|
||||||
@ -313,7 +315,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: contentTheme.buttonColor,
|
||||||
icon: const Icon(Icons.assignment),
|
icon: const Icon(Icons.assignment),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
'Assign to Project',
|
'Assign to Project',
|
||||||
|
|||||||
@ -267,7 +267,7 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.red,
|
color: contentTheme.buttonColor,
|
||||||
borderRadius: BorderRadius.circular(28),
|
borderRadius: BorderRadius.circular(28),
|
||||||
boxShadow: const [
|
boxShadow: const [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
@ -426,11 +426,11 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
|
|||||||
value: _employeeController.isAllEmployeeSelected.value,
|
value: _employeeController.isAllEmployeeSelected.value,
|
||||||
onChanged: (_) => Navigator.pop(context, 'all_employees'),
|
onChanged: (_) => Navigator.pop(context, 'all_employees'),
|
||||||
checkColor: Colors.white,
|
checkColor: Colors.white,
|
||||||
activeColor: Colors.blueAccent,
|
activeColor: contentTheme.buttonColor,
|
||||||
side: const BorderSide(color: Colors.black, width: 1.5),
|
side: const BorderSide(color: Colors.black, width: 1.5),
|
||||||
fillColor: MaterialStateProperty.resolveWith<Color>(
|
fillColor: MaterialStateProperty.resolveWith<Color>(
|
||||||
(states) => states.contains(MaterialState.selected)
|
(states) => states.contains(MaterialState.selected)
|
||||||
? Colors.blueAccent
|
? contentTheme.buttonColor
|
||||||
: Colors.white),
|
: Colors.white),
|
||||||
),
|
),
|
||||||
const Text('All Employees'),
|
const Text('All Employees'),
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import 'package:marco/helpers/widgets/my_text.dart';
|
|||||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||||
import 'package:marco/model/employees/employee_info.dart';
|
import 'package:marco/model/employees/employee_info.dart';
|
||||||
import 'package:timeline_tile/timeline_tile.dart';
|
import 'package:timeline_tile/timeline_tile.dart';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
|
||||||
class ExpenseDetailScreen extends StatefulWidget {
|
class ExpenseDetailScreen extends StatefulWidget {
|
||||||
final String expenseId;
|
final String expenseId;
|
||||||
@ -30,7 +31,7 @@ class ExpenseDetailScreen extends StatefulWidget {
|
|||||||
State<ExpenseDetailScreen> createState() => _ExpenseDetailScreenState();
|
State<ExpenseDetailScreen> createState() => _ExpenseDetailScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ExpenseDetailScreenState extends State<ExpenseDetailScreen> {
|
class _ExpenseDetailScreenState extends State<ExpenseDetailScreen> with UIMixin {
|
||||||
final controller = Get.put(ExpenseDetailController());
|
final controller = Get.put(ExpenseDetailController());
|
||||||
final projectController = Get.find<ProjectController>();
|
final projectController = Get.find<ProjectController>();
|
||||||
final permissionController = Get.put(PermissionController());
|
final permissionController = Get.put(PermissionController());
|
||||||
@ -195,7 +196,7 @@ final permissionController = Get.put(PermissionController());
|
|||||||
await showAddExpenseBottomSheet(isEdit: true);
|
await showAddExpenseBottomSheet(isEdit: true);
|
||||||
await controller.fetchExpenseDetails();
|
await controller.fetchExpenseDetails();
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: contentTheme.buttonColor,
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
label: MyText.bodyMedium(
|
label: MyText.bodyMedium(
|
||||||
"Edit Expense", fontWeight: 600, color: Colors.white),
|
"Edit Expense", fontWeight: 600, color: Colors.white),
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// ignore_for_file: must_be_immutable
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:marco/controller/expense/expense_screen_controller.dart';
|
import 'package:marco/controller/expense/expense_screen_controller.dart';
|
||||||
@ -8,8 +10,9 @@ import 'package:marco/helpers/widgets/my_text.dart';
|
|||||||
import 'package:marco/helpers/widgets/my_text_style.dart';
|
import 'package:marco/helpers/widgets/my_text_style.dart';
|
||||||
import 'package:marco/model/employees/employee_model.dart';
|
import 'package:marco/model/employees/employee_model.dart';
|
||||||
import 'package:marco/model/expense/employee_selector_for_filter_bottom_sheet.dart';
|
import 'package:marco/model/expense/employee_selector_for_filter_bottom_sheet.dart';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
|
||||||
class ExpenseFilterBottomSheet extends StatelessWidget {
|
class ExpenseFilterBottomSheet extends StatefulWidget {
|
||||||
final ExpenseController expenseController;
|
final ExpenseController expenseController;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
|
||||||
@ -19,12 +22,17 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ExpenseFilterBottomSheet> createState() =>
|
||||||
|
_ExpenseFilterBottomSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExpenseFilterBottomSheetState extends State<ExpenseFilterBottomSheet>
|
||||||
|
with UIMixin {
|
||||||
// FIX: create search adapter
|
// FIX: create search adapter
|
||||||
Future<List<EmployeeModel>> searchEmployeesForBottomSheet(
|
Future<List<EmployeeModel>> searchEmployeesForBottomSheet(String query) async {
|
||||||
String query) async {
|
await widget.expenseController.searchEmployees(query);
|
||||||
await expenseController
|
return widget.expenseController.employeeSearchResults.toList();
|
||||||
.searchEmployees(query); // async method, returns void
|
|
||||||
return expenseController.employeeSearchResults.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -34,21 +42,21 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
title: 'Filter Expenses',
|
title: 'Filter Expenses',
|
||||||
onCancel: () => Get.back(),
|
onCancel: () => Get.back(),
|
||||||
onSubmit: () {
|
onSubmit: () {
|
||||||
expenseController.fetchExpenses();
|
widget.expenseController.fetchExpenses();
|
||||||
Get.back();
|
Get.back();
|
||||||
},
|
},
|
||||||
submitText: 'Submit',
|
submitText: 'Submit',
|
||||||
submitColor: Colors.indigo,
|
submitColor: contentTheme.buttonColor,
|
||||||
submitIcon: Icons.check_circle_outline,
|
submitIcon: Icons.check_circle_outline,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: scrollController,
|
controller: widget.scrollController,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () => expenseController.clearFilters(),
|
onPressed: () => widget.expenseController.clearFilters(),
|
||||||
child: MyText(
|
child: MyText(
|
||||||
"Reset Filter",
|
"Reset Filter",
|
||||||
style: MyTextStyle.labelMedium(
|
style: MyTextStyle.labelMedium(
|
||||||
@ -91,11 +99,12 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
"Project",
|
"Project",
|
||||||
_popupSelector(
|
_popupSelector(
|
||||||
context,
|
context,
|
||||||
currentValue: expenseController.selectedProject.value.isEmpty
|
currentValue: widget.expenseController.selectedProject.value.isEmpty
|
||||||
? 'Select Project'
|
? 'Select Project'
|
||||||
: expenseController.selectedProject.value,
|
: widget.expenseController.selectedProject.value,
|
||||||
items: expenseController.globalProjects,
|
items: widget.expenseController.globalProjects,
|
||||||
onSelected: (value) => expenseController.selectedProject.value = value,
|
onSelected: (value) =>
|
||||||
|
widget.expenseController.selectedProject.value = value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -105,18 +114,19 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
"Expense Status",
|
"Expense Status",
|
||||||
_popupSelector(
|
_popupSelector(
|
||||||
context,
|
context,
|
||||||
currentValue: expenseController.selectedStatus.value.isEmpty
|
currentValue: widget.expenseController.selectedStatus.value.isEmpty
|
||||||
? 'Select Expense Status'
|
? 'Select Expense Status'
|
||||||
: expenseController.expenseStatuses
|
: widget.expenseController.expenseStatuses
|
||||||
.firstWhereOrNull(
|
.firstWhereOrNull((e) =>
|
||||||
(e) => e.id == expenseController.selectedStatus.value)
|
e.id == widget.expenseController.selectedStatus.value)
|
||||||
?.name ??
|
?.name ??
|
||||||
'Select Expense Status',
|
'Select Expense Status',
|
||||||
items: expenseController.expenseStatuses.map((e) => e.name).toList(),
|
items:
|
||||||
|
widget.expenseController.expenseStatuses.map((e) => e.name).toList(),
|
||||||
onSelected: (name) {
|
onSelected: (name) {
|
||||||
final status = expenseController.expenseStatuses
|
final status = widget.expenseController.expenseStatuses
|
||||||
.firstWhere((e) => e.name == name);
|
.firstWhere((e) => e.name == name);
|
||||||
expenseController.selectedStatus.value = status.id;
|
widget.expenseController.selectedStatus.value = status.id;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -130,14 +140,13 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Obx(() {
|
Obx(() {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity, // Make it full width
|
width: double.infinity,
|
||||||
child: SegmentedButton<String>(
|
child: SegmentedButton<String>(
|
||||||
segments: expenseController.dateTypes
|
segments: widget.expenseController.dateTypes
|
||||||
.map(
|
.map(
|
||||||
(type) => ButtonSegment(
|
(type) => ButtonSegment(
|
||||||
value: type,
|
value: type,
|
||||||
label: Center(
|
label: Center(
|
||||||
// Center label text
|
|
||||||
child: MyText(
|
child: MyText(
|
||||||
type,
|
type,
|
||||||
style: MyTextStyle.bodySmall(
|
style: MyTextStyle.bodySmall(
|
||||||
@ -150,10 +159,10 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
selected: {expenseController.selectedDateType.value},
|
selected: {widget.expenseController.selectedDateType.value},
|
||||||
onSelectionChanged: (newSelection) {
|
onSelectionChanged: (newSelection) {
|
||||||
if (newSelection.isNotEmpty) {
|
if (newSelection.isNotEmpty) {
|
||||||
expenseController.selectedDateType.value =
|
widget.expenseController.selectedDateType.value =
|
||||||
newSelection.first;
|
newSelection.first;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -195,28 +204,30 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _dateButton(
|
child: _dateButton(
|
||||||
label: expenseController.startDate.value == null
|
label: widget.expenseController.startDate.value == null
|
||||||
? 'Start Date'
|
? 'Start Date'
|
||||||
: DateTimeUtils.formatDate(
|
: DateTimeUtils.formatDate(
|
||||||
expenseController.startDate.value!, 'dd MMM yyyy'),
|
widget.expenseController.startDate.value!,
|
||||||
|
'dd MMM yyyy'),
|
||||||
onTap: () => _selectDate(
|
onTap: () => _selectDate(
|
||||||
context,
|
context,
|
||||||
expenseController.startDate,
|
widget.expenseController.startDate,
|
||||||
lastDate: expenseController.endDate.value,
|
lastDate: widget.expenseController.endDate.value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MySpacing.width(12),
|
MySpacing.width(12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _dateButton(
|
child: _dateButton(
|
||||||
label: expenseController.endDate.value == null
|
label: widget.expenseController.endDate.value == null
|
||||||
? 'End Date'
|
? 'End Date'
|
||||||
: DateTimeUtils.formatDate(
|
: DateTimeUtils.formatDate(
|
||||||
expenseController.endDate.value!, 'dd MMM yyyy'),
|
widget.expenseController.endDate.value!,
|
||||||
|
'dd MMM yyyy'),
|
||||||
onTap: () => _selectDate(
|
onTap: () => _selectDate(
|
||||||
context,
|
context,
|
||||||
expenseController.endDate,
|
widget.expenseController.endDate,
|
||||||
firstDate: expenseController.startDate.value,
|
firstDate: widget.expenseController.startDate.value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -232,8 +243,8 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
"Paid By",
|
"Paid By",
|
||||||
_employeeSelector(
|
_employeeSelector(
|
||||||
context: context,
|
context: context,
|
||||||
selectedEmployees: expenseController.selectedPaidByEmployees,
|
selectedEmployees: widget.expenseController.selectedPaidByEmployees,
|
||||||
searchEmployees: searchEmployeesForBottomSheet, // FIXED
|
searchEmployees: searchEmployeesForBottomSheet,
|
||||||
title: 'Search Paid By',
|
title: 'Search Paid By',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -244,19 +255,15 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
"Created By",
|
"Created By",
|
||||||
_employeeSelector(
|
_employeeSelector(
|
||||||
context: context,
|
context: context,
|
||||||
selectedEmployees: expenseController.selectedCreatedByEmployees,
|
selectedEmployees: widget.expenseController.selectedCreatedByEmployees,
|
||||||
searchEmployees: searchEmployeesForBottomSheet, // FIXED
|
searchEmployees: searchEmployeesForBottomSheet,
|
||||||
title: 'Search Created By',
|
title: 'Search Created By',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _selectDate(
|
Future<void> _selectDate(BuildContext context, Rx<DateTime?> dateNotifier,
|
||||||
BuildContext context,
|
{DateTime? firstDate, DateTime? lastDate}) async {
|
||||||
Rx<DateTime?> dateNotifier, {
|
|
||||||
DateTime? firstDate,
|
|
||||||
DateTime? lastDate,
|
|
||||||
}) async {
|
|
||||||
final DateTime? picked = await showDatePicker(
|
final DateTime? picked = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: dateNotifier.value ?? DateTime.now(),
|
initialDate: dateNotifier.value ?? DateTime.now(),
|
||||||
@ -268,12 +275,10 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _popupSelector(
|
Widget _popupSelector(BuildContext context,
|
||||||
BuildContext context, {
|
{required String currentValue,
|
||||||
required String currentValue,
|
required List<String> items,
|
||||||
required List<String> items,
|
required ValueChanged<String> onSelected}) {
|
||||||
required ValueChanged<String> onSelected,
|
|
||||||
}) {
|
|
||||||
return PopupMenuButton<String>(
|
return PopupMenuButton<String>(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
@ -374,10 +379,12 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
return Wrap(
|
return Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: selectedEmployees
|
children: selectedEmployees
|
||||||
.map((emp) => Chip(
|
.map(
|
||||||
label: MyText(emp.name),
|
(emp) => Chip(
|
||||||
onDeleted: () => selectedEmployees.remove(emp),
|
label: MyText(emp.name),
|
||||||
))
|
onDeleted: () => selectedEmployees.remove(emp),
|
||||||
|
),
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -408,5 +415,4 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import 'package:marco/view/expense/expense_filter_bottom_sheet.dart';
|
|||||||
import 'package:marco/helpers/widgets/expense/expense_main_components.dart';
|
import 'package:marco/helpers/widgets/expense/expense_main_components.dart';
|
||||||
import 'package:marco/helpers/utils/permission_constants.dart';
|
import 'package:marco/helpers/utils/permission_constants.dart';
|
||||||
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
|
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
|
||||||
class ExpenseMainScreen extends StatefulWidget {
|
class ExpenseMainScreen extends StatefulWidget {
|
||||||
const ExpenseMainScreen({super.key});
|
const ExpenseMainScreen({super.key});
|
||||||
@ -21,12 +21,12 @@ class ExpenseMainScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ExpenseMainScreenState extends State<ExpenseMainScreen>
|
class _ExpenseMainScreenState extends State<ExpenseMainScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin, UIMixin {
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
final searchController = TextEditingController();
|
final searchController = TextEditingController();
|
||||||
final expenseController = Get.put(ExpenseController());
|
final expenseController = Get.put(ExpenseController());
|
||||||
final projectController = Get.find<ProjectController>();
|
final projectController = Get.find<ProjectController>();
|
||||||
final permissionController = Get.put(PermissionController());
|
final permissionController = Get.put(PermissionController());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -81,83 +81,85 @@ final permissionController = Get.put(PermissionController());
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
appBar: ExpenseAppBar(projectController: projectController),
|
appBar: ExpenseAppBar(projectController: projectController),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
// ---------------- TabBar ----------------
|
// ---------------- TabBar ----------------
|
||||||
Container(
|
Container(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
labelColor: Colors.black,
|
labelColor: Colors.black,
|
||||||
unselectedLabelColor: Colors.grey,
|
unselectedLabelColor: Colors.grey,
|
||||||
indicatorColor: Colors.red,
|
indicatorColor: Colors.red,
|
||||||
tabs: const [
|
tabs: const [
|
||||||
Tab(text: "Current Month"),
|
Tab(text: "Current Month"),
|
||||||
Tab(text: "History"),
|
Tab(text: "History"),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// ---------------- Gray background for rest ----------------
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
color: Colors.grey[100],
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// ---------------- Search ----------------
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
|
|
||||||
child: SearchAndFilter(
|
|
||||||
controller: searchController,
|
|
||||||
onChanged: (_) => setState(() {}),
|
|
||||||
onFilterTap: _openFilterBottomSheet,
|
|
||||||
expenseController: expenseController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// ---------------- TabBarView ----------------
|
|
||||||
Expanded(
|
|
||||||
child: TabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
children: [
|
|
||||||
_buildExpenseList(isHistory: false),
|
|
||||||
_buildExpenseList(isHistory: true),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// ✅ FAB reacts only to upload permission
|
// ---------------- Gray background for rest ----------------
|
||||||
floatingActionButton: Obx(() {
|
Expanded(
|
||||||
// Show loader or hide FAB while permissions are loading
|
child: Container(
|
||||||
if (permissionController.permissions.isEmpty) {
|
color: Colors.grey[100],
|
||||||
return const SizedBox.shrink();
|
child: Column(
|
||||||
}
|
children: [
|
||||||
|
// ---------------- Search ----------------
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
|
||||||
|
child: SearchAndFilter(
|
||||||
|
controller: searchController,
|
||||||
|
onChanged: (_) => setState(() {}),
|
||||||
|
onFilterTap: _openFilterBottomSheet,
|
||||||
|
expenseController: expenseController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
final canUpload =
|
// ---------------- TabBarView ----------------
|
||||||
permissionController.hasPermission(Permissions.expenseUpload);
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: [
|
||||||
|
_buildExpenseList(isHistory: false),
|
||||||
|
_buildExpenseList(isHistory: true),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: Obx(() {
|
||||||
|
// Show loader or hide FAB while permissions are loading
|
||||||
|
if (permissionController.permissions.isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
return canUpload
|
final canUpload =
|
||||||
? FloatingActionButton(
|
permissionController.hasPermission(Permissions.expenseUpload);
|
||||||
backgroundColor: Colors.red,
|
|
||||||
onPressed: showAddExpenseBottomSheet,
|
|
||||||
child: const Icon(Icons.add, color: Colors.white),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return canUpload
|
||||||
|
? FloatingActionButton.extended(
|
||||||
|
backgroundColor: contentTheme.buttonColor,
|
||||||
|
onPressed: showAddExpenseBottomSheet,
|
||||||
|
icon: const Icon(Icons.add, color: Colors.white),
|
||||||
|
label: const Text(
|
||||||
|
"Add Expense",
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildExpenseList({required bool isHistory}) {
|
Widget _buildExpenseList({required bool isHistory}) {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
@ -207,4 +209,3 @@ Widget build(BuildContext context) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
import 'package:marco/images.dart';
|
import 'package:marco/images.dart';
|
||||||
|
import 'package:marco/helpers/widgets/wave_background.dart';
|
||||||
|
|
||||||
|
|
||||||
class OfflineScreen extends StatefulWidget {
|
class OfflineScreen extends StatefulWidget {
|
||||||
const OfflineScreen({super.key});
|
const OfflineScreen({super.key});
|
||||||
@ -39,12 +41,11 @@ class _OfflineScreenState extends State<OfflineScreen>
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
_RedWaveBackground(brandRed: contentTheme.brandRed),
|
RedWaveBackground(brandRed: contentTheme.brandColor),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
horizontal: 12.0),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -69,14 +70,14 @@ class _OfflineScreenState extends State<OfflineScreen>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Increased spacing here
|
// Increased spacing here
|
||||||
const SizedBox(height: 120),
|
const SizedBox(height: 120),
|
||||||
const Icon(Icons.wifi_off,
|
const Icon(Icons.wifi_off,
|
||||||
size: 100, color: Colors.redAccent),
|
size: 100, color: Colors.redAccent),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
const Text(
|
const Text(
|
||||||
"No Internet Connection",
|
"No Internet Connection",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
@ -88,7 +89,6 @@ class _OfflineScreenState extends State<OfflineScreen>
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||||
),
|
),
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -99,58 +99,3 @@ class _OfflineScreenState extends State<OfflineScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RedWaveBackground extends StatelessWidget {
|
|
||||||
final Color brandRed;
|
|
||||||
const _RedWaveBackground({required this.brandRed});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return CustomPaint(
|
|
||||||
painter: _WavePainter(brandRed),
|
|
||||||
size: Size.infinite,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WavePainter extends CustomPainter {
|
|
||||||
final Color brandRed;
|
|
||||||
|
|
||||||
_WavePainter(this.brandRed);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final paint1 = Paint()
|
|
||||||
..shader = LinearGradient(
|
|
||||||
colors: [brandRed, const Color.fromARGB(255, 97, 22, 22)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
|
|
||||||
|
|
||||||
final path1 = Path()
|
|
||||||
..moveTo(0, size.height * 0.2)
|
|
||||||
..quadraticBezierTo(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)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
|
|
||||||
canvas.drawPath(path1, paint1);
|
|
||||||
|
|
||||||
final paint2 = Paint()..color = Colors.redAccent.withOpacity(0.15);
|
|
||||||
final path2 = Path()
|
|
||||||
..moveTo(0, size.height * 0.25)
|
|
||||||
..quadraticBezierTo(
|
|
||||||
size.width * 0.4, size.height * 0.1, size.width, size.height * 0.2)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
|
|
||||||
canvas.drawPath(path2, paint2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
@ -13,7 +13,15 @@ import 'package:marco/view/employees/employee_profile_screen.dart';
|
|||||||
import 'package:marco/helpers/services/tenant_service.dart';
|
import 'package:marco/helpers/services/tenant_service.dart';
|
||||||
import 'package:marco/view/tenant/tenant_selection_screen.dart';
|
import 'package:marco/view/tenant/tenant_selection_screen.dart';
|
||||||
import 'package:marco/controller/tenant/tenant_switch_controller.dart';
|
import 'package:marco/controller/tenant/tenant_switch_controller.dart';
|
||||||
|
import 'package:marco/helpers/theme/theme_editor_widget.dart';
|
||||||
|
|
||||||
|
class ThemeOption {
|
||||||
|
final String label;
|
||||||
|
final Color primary;
|
||||||
|
final Color button;
|
||||||
|
final Color brand;
|
||||||
|
ThemeOption(this.label, this.primary, this.button, this.brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class UserProfileBar extends StatefulWidget {
|
class UserProfileBar extends StatefulWidget {
|
||||||
@ -29,6 +37,7 @@ class _UserProfileBarState extends State<UserProfileBar>
|
|||||||
late EmployeeInfo employeeInfo;
|
late EmployeeInfo employeeInfo;
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
bool hasMpin = true;
|
bool hasMpin = true;
|
||||||
|
bool _isThemeEditorVisible = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -58,8 +67,8 @@ class _UserProfileBarState extends State<UserProfileBar>
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
Colors.white.withValues(alpha: 0.95),
|
Colors.white.withOpacity(0.95),
|
||||||
Colors.white.withValues(alpha: 0.85),
|
Colors.white.withOpacity(0.85),
|
||||||
],
|
],
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
@ -67,150 +76,158 @@ class _UserProfileBarState extends State<UserProfileBar>
|
|||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withValues(alpha: 0.06),
|
color: Colors.black.withOpacity(0.06),
|
||||||
blurRadius: 18,
|
blurRadius: 18,
|
||||||
offset: const Offset(0, 8),
|
offset: const Offset(0, 8),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.grey.withValues(alpha: 0.25),
|
color: Colors.grey.withOpacity(0.25),
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
bottom: true,
|
bottom: true,
|
||||||
child: Column(
|
child: Stack(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
children: [
|
||||||
children: [
|
Offstage(
|
||||||
_isLoading
|
offstage: _isThemeEditorVisible,
|
||||||
? const _LoadingSection()
|
child: Column(
|
||||||
: _userProfileSection(isCondensed),
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
// --- SWITCH TENANT ROW BELOW AVATAR ---
|
_isLoading
|
||||||
if (!_isLoading && !isCondensed) _switchTenantRow(),
|
? const _LoadingSection()
|
||||||
|
: _userProfileSection(isCondensed),
|
||||||
MySpacing.height(12),
|
if (!_isLoading && !isCondensed) _switchTenantRow(),
|
||||||
Divider(
|
MySpacing.height(12),
|
||||||
indent: 18,
|
Divider(
|
||||||
endIndent: 18,
|
indent: 18,
|
||||||
thickness: 0.7,
|
endIndent: 18,
|
||||||
color: Colors.grey.withValues(alpha: 0.25),
|
thickness: 0.7,
|
||||||
),
|
color: Colors.grey.withOpacity(0.25),
|
||||||
MySpacing.height(12),
|
),
|
||||||
_supportAndSettingsMenu(isCondensed),
|
MySpacing.height(12),
|
||||||
const Spacer(),
|
_supportAndSettingsMenu(isCondensed),
|
||||||
Divider(
|
const Spacer(),
|
||||||
indent: 18,
|
Divider(
|
||||||
endIndent: 18,
|
indent: 18,
|
||||||
thickness: 0.35,
|
endIndent: 18,
|
||||||
color: Colors.grey.withValues(alpha: 0.18),
|
thickness: 0.35,
|
||||||
),
|
color: Colors.grey.withOpacity(0.18),
|
||||||
_logoutButton(isCondensed),
|
),
|
||||||
],
|
_logoutButton(isCondensed),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
Offstage(
|
||||||
|
offstage: !_isThemeEditorVisible,
|
||||||
|
child: ThemeEditorWidget(
|
||||||
|
onClose: () {
|
||||||
|
setState(() => _isThemeEditorVisible = false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Row widget to switch tenant with popup menu (button only)
|
// ==================== CONTINUE EXISTING CODE =====================
|
||||||
/// Row widget to switch tenant with popup menu (button only)
|
Widget _switchTenantRow() {
|
||||||
Widget _switchTenantRow() {
|
final TenantSwitchController tenantSwitchController =
|
||||||
// Use the dedicated switch controller
|
Get.put(TenantSwitchController());
|
||||||
final TenantSwitchController tenantSwitchController =
|
|
||||||
Get.put(TenantSwitchController());
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
if (tenantSwitchController.isLoading.value) {
|
if (tenantSwitchController.isLoading.value) {
|
||||||
return _loadingTenantContainer();
|
return _loadingTenantContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
final tenants = tenantSwitchController.tenants;
|
final tenants = tenantSwitchController.tenants;
|
||||||
if (tenants.isEmpty) return _noTenantContainer();
|
if (tenants.isEmpty) return _noTenantContainer();
|
||||||
|
|
||||||
final selectedTenant = TenantService.currentTenant;
|
final selectedTenant = TenantService.currentTenant;
|
||||||
|
|
||||||
// Sort tenants: selected tenant first
|
final sortedTenants = List.of(tenants);
|
||||||
final sortedTenants = List.of(tenants);
|
if (selectedTenant != null) {
|
||||||
if (selectedTenant != null) {
|
sortedTenants.sort((a, b) {
|
||||||
sortedTenants.sort((a, b) {
|
if (a.id == selectedTenant.id) return -1;
|
||||||
if (a.id == selectedTenant.id) return -1;
|
if (b.id == selectedTenant.id) return 1;
|
||||||
if (b.id == selectedTenant.id) return 1;
|
return 0;
|
||||||
return 0;
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return PopupMenuButton<String>(
|
return PopupMenuButton<String>(
|
||||||
onSelected: (tenantId) =>
|
onSelected: (tenantId) =>
|
||||||
tenantSwitchController.switchTenant(tenantId),
|
tenantSwitchController.switchTenant(tenantId),
|
||||||
itemBuilder: (_) => sortedTenants.map((tenant) {
|
itemBuilder: (_) => sortedTenants.map((tenant) {
|
||||||
return PopupMenuItem(
|
return PopupMenuItem(
|
||||||
value: tenant.id,
|
value: tenant.id,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
color: Colors.grey.shade200,
|
color: Colors.grey.shade200,
|
||||||
child: TenantLogo(logoImage: tenant.logoImage),
|
child: TenantLogo(logoImage: tenant.logoImage),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 10),
|
||||||
const SizedBox(width: 10),
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
tenant.name,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: tenant.id == selectedTenant?.id
|
||||||
|
? FontWeight.bold
|
||||||
|
: FontWeight.w600,
|
||||||
|
color: tenant.id == selectedTenant?.id
|
||||||
|
? contentTheme.buttonColor
|
||||||
|
: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (tenant.id == selectedTenant?.id)
|
||||||
|
Icon(Icons.check_circle,
|
||||||
|
color: contentTheme.buttonColor, size: 18),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.swap_horiz, color: contentTheme.buttonColor),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Padding(
|
||||||
tenant.name,
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
maxLines: 1,
|
child: Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
"Switch Organization",
|
||||||
style: TextStyle(
|
maxLines: 1,
|
||||||
fontWeight: tenant.id == selectedTenant?.id
|
overflow: TextOverflow.ellipsis,
|
||||||
? FontWeight.bold
|
style: TextStyle(
|
||||||
: FontWeight.w600,
|
color: contentTheme.buttonColor,
|
||||||
color: tenant.id == selectedTenant?.id
|
fontWeight: FontWeight.bold),
|
||||||
? Colors.blueAccent
|
|
||||||
: Colors.black87,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (tenant.id == selectedTenant?.id)
|
Icon(Icons.arrow_drop_down, color: contentTheme.buttonColor),
|
||||||
const Icon(Icons.check_circle,
|
|
||||||
color: Colors.blueAccent, size: 18),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.swap_horiz, color: Colors.blue.shade600),
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
|
||||||
child: Text(
|
|
||||||
"Switch Organization",
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blue, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(Icons.arrow_drop_down, color: Colors.blue.shade600),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}),
|
||||||
}),
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Widget _loadingTenantContainer() => Container(
|
Widget _loadingTenantContainer() => Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||||
@ -254,7 +271,7 @@ Widget _switchTenantRow() {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Theme.of(context).primaryColor.withValues(alpha: 0.15),
|
color: Theme.of(context).primaryColor.withOpacity(0.15),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
spreadRadius: 1,
|
spreadRadius: 1,
|
||||||
),
|
),
|
||||||
@ -310,6 +327,11 @@ Widget _switchTenantRow() {
|
|||||||
_menuItemRow(
|
_menuItemRow(
|
||||||
icon: LucideIcons.settings,
|
icon: LucideIcons.settings,
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isThemeEditorVisible = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: spacingHeight),
|
SizedBox(height: spacingHeight),
|
||||||
_menuItemRow(
|
_menuItemRow(
|
||||||
@ -320,8 +342,6 @@ Widget _switchTenantRow() {
|
|||||||
_menuItemRow(
|
_menuItemRow(
|
||||||
icon: LucideIcons.lock,
|
icon: LucideIcons.lock,
|
||||||
label: hasMpin ? 'Change MPIN' : 'Set MPIN',
|
label: hasMpin ? 'Change MPIN' : 'Set MPIN',
|
||||||
iconColor: Colors.redAccent,
|
|
||||||
textColor: Colors.redAccent,
|
|
||||||
onTap: _onMpinTap,
|
onTap: _onMpinTap,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -395,9 +415,9 @@ Widget _switchTenantRow() {
|
|||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
),
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red.shade600,
|
backgroundColor: contentTheme.buttonColor,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
shadowColor: Colors.red.shade200,
|
shadowColor: contentTheme.buttonColor,
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: condensed ? 14 : 18,
|
vertical: condensed ? 14 : 18,
|
||||||
horizontal: condensed ? 14 : 22,
|
horizontal: condensed ? 14 : 22,
|
||||||
@ -419,54 +439,71 @@ Widget _switchTenantRow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLogoutDialog(BuildContext context) {
|
Widget _buildLogoutDialog(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final primaryColor = contentTheme.buttonColor;
|
||||||
|
|
||||||
return Dialog(
|
return Dialog(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||||
elevation: 10,
|
elevation: 10,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: theme.cardColor,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 34),
|
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 34),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(LucideIcons.log_out, size: 56, color: Colors.red.shade700),
|
// Top icon
|
||||||
const SizedBox(height: 18),
|
Icon(LucideIcons.log_out, size: 56, color: primaryColor),
|
||||||
const Text(
|
MySpacing.height(18),
|
||||||
|
// Title
|
||||||
|
MyText.titleLarge(
|
||||||
"Logout Confirmation",
|
"Logout Confirmation",
|
||||||
style: TextStyle(
|
fontWeight: 700,
|
||||||
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.",
|
|
||||||
textAlign: TextAlign.center,
|
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(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context, false),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.grey.shade700,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12)),
|
|
||||||
child: const Text("Cancel"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 18),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () => Navigator.pop(context, true),
|
onPressed: () => Navigator.pop(context, false),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red.shade700,
|
backgroundColor: Colors.grey,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(14)),
|
borderRadius: BorderRadius.circular(14)),
|
||||||
),
|
),
|
||||||
child: const Text("Logout"),
|
child: MyText.bodyMedium(
|
||||||
|
"Cancel",
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: 600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.width(18),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(14)),
|
||||||
|
),
|
||||||
|
child: MyText.bodyMedium(
|
||||||
|
"Logout",
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: 600,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import 'package:marco/helpers/widgets/my_text.dart';
|
|||||||
import 'package:marco/images.dart';
|
import 'package:marco/images.dart';
|
||||||
import 'package:marco/controller/tenant/tenant_selection_controller.dart';
|
import 'package:marco/controller/tenant/tenant_selection_controller.dart';
|
||||||
import 'package:marco/view/splash_screen.dart';
|
import 'package:marco/view/splash_screen.dart';
|
||||||
|
import 'package:marco/helpers/widgets/wave_background.dart';
|
||||||
|
|
||||||
class TenantSelectionScreen extends StatefulWidget {
|
class TenantSelectionScreen extends StatefulWidget {
|
||||||
const TenantSelectionScreen({super.key});
|
const TenantSelectionScreen({super.key});
|
||||||
@ -63,7 +64,7 @@ class _TenantSelectionScreenState extends State<TenantSelectionScreen>
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
_RedWaveBackground(brandRed: contentTheme.brandRed),
|
RedWaveBackground(brandRed: contentTheme.brandColor),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -188,12 +189,12 @@ class _BetaBadge extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Tenant Card List
|
/// Tenant Card List
|
||||||
class TenantCardList extends StatelessWidget {
|
class TenantCardList extends StatelessWidget with UIMixin {
|
||||||
final TenantSelectionController controller;
|
final TenantSelectionController controller;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final Function(String tenantId) onTenantSelected;
|
final Function(String tenantId) onTenantSelected;
|
||||||
|
|
||||||
const TenantCardList({
|
TenantCardList({
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.isLoading,
|
required this.isLoading,
|
||||||
required this.onTenantSelected,
|
required this.onTenantSelected,
|
||||||
@ -220,24 +221,23 @@ class TenantCardList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
if (hasTenants)
|
||||||
if (hasTenants) ...controller.tenants.map(
|
...controller.tenants.map(
|
||||||
(tenant) => _TenantCard(
|
(tenant) => _TenantCard(
|
||||||
tenant: tenant,
|
tenant: tenant,
|
||||||
onTap: () => onTenantSelected(tenant.id),
|
onTap: () => onTenantSelected(tenant.id),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await LocalStorage.logout();
|
await LocalStorage.logout();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.arrow_back, size: 20, color: Colors.redAccent),
|
icon:
|
||||||
|
Icon(Icons.arrow_back, size: 20, color: contentTheme.brandColor,),
|
||||||
label: MyText(
|
label: MyText(
|
||||||
'Back to Login',
|
'Back to Login',
|
||||||
color: Colors.red,
|
color: contentTheme.brandColor,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
@ -249,10 +249,10 @@ class TenantCardList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Single Tenant Card
|
/// Single Tenant Card
|
||||||
class _TenantCard extends StatelessWidget {
|
class _TenantCard extends StatelessWidget with UIMixin {
|
||||||
final dynamic tenant;
|
final dynamic tenant;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
const _TenantCard({required this.tenant, required this.onTap});
|
_TenantCard({required this.tenant, required this.onTap});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -297,7 +297,7 @@ class _TenantCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Icon(Icons.arrow_forward_ios, size: 24, color: Colors.red),
|
Icon(Icons.arrow_forward_ios, size: 24, color: contentTheme.brandColor,),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -335,55 +335,3 @@ class TenantLogo extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Red Wave Background
|
|
||||||
class _RedWaveBackground extends StatelessWidget {
|
|
||||||
final Color brandRed;
|
|
||||||
const _RedWaveBackground({required this.brandRed});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return CustomPaint(
|
|
||||||
painter: _WavePainter(brandRed),
|
|
||||||
size: Size.infinite,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WavePainter extends CustomPainter {
|
|
||||||
final Color brandRed;
|
|
||||||
_WavePainter(this.brandRed);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final paint1 = Paint()
|
|
||||||
..shader = LinearGradient(
|
|
||||||
colors: [brandRed, const Color.fromARGB(255, 97, 22, 22)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
|
|
||||||
|
|
||||||
final path1 = Path()
|
|
||||||
..moveTo(0, size.height * 0.2)
|
|
||||||
..quadraticBezierTo(
|
|
||||||
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)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
canvas.drawPath(path1, paint1);
|
|
||||||
|
|
||||||
final paint2 = Paint()..color = Colors.redAccent.withOpacity(0.15);
|
|
||||||
final path2 = Path()
|
|
||||||
..moveTo(0, size.height * 0.25)
|
|
||||||
..quadraticBezierTo(size.width * 0.4, size.height * 0.1, size.width, size.height * 0.2)
|
|
||||||
..lineTo(size.width, 0)
|
|
||||||
..lineTo(0, 0)
|
|
||||||
..close();
|
|
||||||
canvas.drawPath(path2, paint2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user