- Created generated_plugin_registrant.cc and generated_plugin_registrant.h to manage plugin registration. - Added generated_plugins.cmake for plugin configuration in CMake. - Implemented CMakeLists.txt for the Windows runner, defining build settings and dependencies. - Created Runner.rc for application resources including versioning and icons. - Developed flutter_window.cpp and flutter_window.h to manage the Flutter window lifecycle. - Implemented main.cpp as the entry point for the Windows application. - Added resource.h for resource definitions. - Included app icon in resources. - Created runner.exe.manifest for application settings. - Developed utils.cpp and utils.h for console management and command line argument handling. - Implemented win32_window.cpp and win32_window.h for high DPI-aware window management.
480 lines
15 KiB
Dart
480 lines
15 KiB
Dart
import 'package:marco/helpers/theme/theme_customizer.dart';
|
|
import 'package:marco/helpers/services/url_service.dart';
|
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
|
import 'package:marco/helpers/utils/my_shadow.dart';
|
|
import 'package:marco/helpers/widgets/my_card.dart';
|
|
import 'package:marco/helpers/widgets/my_container.dart';
|
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
|
import 'package:marco/helpers/widgets/my_text.dart';
|
|
import 'package:marco/images.dart';
|
|
import 'package:marco/widgets/custom_pop_menu.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/route_manager.dart';
|
|
import 'package:flutter_lucide/flutter_lucide.dart';
|
|
|
|
typedef LeftbarMenuFunction = void Function(String key);
|
|
|
|
class LeftbarObserver {
|
|
static Map<String, LeftbarMenuFunction> observers = {};
|
|
|
|
static attachListener(String key, LeftbarMenuFunction fn) {
|
|
observers[key] = fn;
|
|
}
|
|
|
|
static detachListener(String key) {
|
|
observers.remove(key);
|
|
}
|
|
|
|
static notifyAll(String key) {
|
|
for (var fn in observers.values) {
|
|
fn(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
class LeftBar extends StatefulWidget {
|
|
final bool isCondensed;
|
|
|
|
const LeftBar({super.key, this.isCondensed = false});
|
|
|
|
@override
|
|
_LeftBarState createState() => _LeftBarState();
|
|
}
|
|
|
|
class _LeftBarState extends State<LeftBar> with SingleTickerProviderStateMixin, UIMixin {
|
|
final ThemeCustomizer customizer = ThemeCustomizer.instance;
|
|
|
|
bool isCondensed = false;
|
|
String path = UrlService.getCurrentUrl();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
isCondensed = widget.isCondensed;
|
|
return MyCard(
|
|
paddingAll: 0,
|
|
shadow: MyShadow(position: MyShadowPosition.centerRight, elevation: 0.2),
|
|
child: AnimatedContainer(
|
|
color: leftBarTheme.background,
|
|
width: isCondensed ? 70 : 250,
|
|
curve: Curves.easeInOut,
|
|
duration: Duration(milliseconds: 200),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Center(
|
|
child: Padding(
|
|
padding: MySpacing.y(13),
|
|
child: InkWell(
|
|
onTap: () => Get.toNamed('/home'),
|
|
child: Image.asset(
|
|
(ThemeCustomizer.instance.theme == ThemeMode.light
|
|
? (widget.isCondensed ? Images.logoLightSmall : Images.logoLight)
|
|
: (widget.isCondensed ? Images.logoDarkSmall : Images.logoDark)),
|
|
height: 28,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: ScrollConfiguration(
|
|
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
|
child: ListView(
|
|
shrinkWrap: true,
|
|
controller: ScrollController(),
|
|
physics: BouncingScrollPhysics(),
|
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
|
children: [
|
|
Divider(),
|
|
labelWidget("Dashboard"),
|
|
|
|
NavigationItem(iconData: LucideIcons.layout_template, title: "Attendance", isCondensed: isCondensed, route: '/dashboard/attendance'),
|
|
],
|
|
),
|
|
)),
|
|
if (!isCondensed) Divider(),
|
|
if (!isCondensed)
|
|
MyContainer.transparent(
|
|
paddingAll: 12,
|
|
child: Row(
|
|
children: [
|
|
MyContainer.rounded(
|
|
height: 46,
|
|
width: 46,
|
|
paddingAll: 0,
|
|
child: Image.asset(Images.avatars[0]),
|
|
),
|
|
MySpacing.width(16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
MyText.labelMedium("Jonathan", fontWeight: 600),
|
|
MySpacing.height(8),
|
|
MyText.labelSmall("jonathan@gmail.com", fontWeight: 600, muted: true),
|
|
],
|
|
),
|
|
),
|
|
MyContainer(
|
|
onTap: () {
|
|
Get.toNamed('/auth/login');
|
|
},
|
|
color: leftBarTheme.activeItemBackground,
|
|
paddingAll: 8,
|
|
child: Icon(LucideIcons.log_out, size: 16, color: leftBarTheme.activeItemColor),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget labelWidget(String label) {
|
|
return isCondensed
|
|
? MySpacing.empty()
|
|
: Container(
|
|
padding: MySpacing.xy(24, 8),
|
|
child: MyText.labelSmall(
|
|
label.toUpperCase(),
|
|
color: leftBarTheme.labelColor,
|
|
muted: true,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.clip,
|
|
fontWeight: 700,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MenuWidget extends StatefulWidget {
|
|
final IconData iconData;
|
|
final String title;
|
|
final bool isCondensed;
|
|
final bool active;
|
|
final List<MenuItem> children;
|
|
|
|
const MenuWidget({super.key, required this.iconData, required this.title, this.isCondensed = false, this.active = false, this.children = const []});
|
|
|
|
@override
|
|
_MenuWidgetState createState() => _MenuWidgetState();
|
|
}
|
|
|
|
class _MenuWidgetState extends State<MenuWidget> with UIMixin, SingleTickerProviderStateMixin {
|
|
bool isHover = false;
|
|
bool isActive = false;
|
|
late Animation<double> _iconTurns;
|
|
late AnimationController _controller;
|
|
bool popupShowing = true;
|
|
Function? hideFn;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController(duration: Duration(milliseconds: 200), vsync: this);
|
|
_iconTurns = _controller.drive(Tween<double>(begin: 0.0, end: 0.5).chain(CurveTween(curve: Curves.easeIn)));
|
|
LeftbarObserver.attachListener(widget.title, onChangeMenuActive);
|
|
}
|
|
|
|
void onChangeMenuActive(String key) {
|
|
if (key != widget.title) {
|
|
// onChangeExpansion(false);
|
|
}
|
|
}
|
|
|
|
void onChangeExpansion(value) {
|
|
isActive = value;
|
|
if (isActive) {
|
|
_controller.forward();
|
|
} else {
|
|
_controller.reverse();
|
|
}
|
|
if (mounted) {
|
|
setState(() {});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
var route = UrlService.getCurrentUrl();
|
|
isActive = widget.children.any((element) => element.route == route);
|
|
onChangeExpansion(isActive);
|
|
if (hideFn != null) {
|
|
hideFn!();
|
|
}
|
|
// popupShowing = false;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// var route = Uri.base.fragment;
|
|
// isActive = widget.children.any((element) => element.route == route);
|
|
|
|
if (widget.isCondensed) {
|
|
return CustomPopupMenu(
|
|
backdrop: true,
|
|
show: popupShowing,
|
|
hideFn: (hide) => hideFn = hide,
|
|
onChange: (_) {
|
|
// popupShowing = _;
|
|
},
|
|
placement: CustomPopupMenuPlacement.right,
|
|
menu: MouseRegion(
|
|
cursor: SystemMouseCursors.click,
|
|
onHover: (event) {
|
|
setState(() {
|
|
isHover = true;
|
|
});
|
|
},
|
|
onExit: (event) {
|
|
setState(() {
|
|
isHover = false;
|
|
});
|
|
},
|
|
child: MyContainer.transparent(
|
|
margin: MySpacing.fromLTRB(16, 0, 16, 8),
|
|
color: isActive || isHover ? leftBarTheme.activeItemBackground : Colors.transparent,
|
|
padding: MySpacing.xy(8, 8),
|
|
child: Center(
|
|
child: Icon(
|
|
widget.iconData,
|
|
color: (isHover || isActive) ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
|
size: 20,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
menuBuilder: (_) => MyContainer.bordered(
|
|
paddingAll: 8,
|
|
width: 190,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: widget.children,
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
return MouseRegion(
|
|
cursor: SystemMouseCursors.click,
|
|
onHover: (event) {
|
|
setState(() {
|
|
isHover = true;
|
|
});
|
|
},
|
|
onExit: (event) {
|
|
setState(() {
|
|
isHover = false;
|
|
});
|
|
},
|
|
child: MyContainer.transparent(
|
|
margin: MySpacing.fromLTRB(24, 0, 16, 0),
|
|
paddingAll: 0,
|
|
child: ListTileTheme(
|
|
contentPadding: EdgeInsets.all(0),
|
|
dense: true,
|
|
horizontalTitleGap: 0.0,
|
|
minLeadingWidth: 0,
|
|
child: ExpansionTile(
|
|
tilePadding: MySpacing.zero,
|
|
initiallyExpanded: isActive,
|
|
maintainState: true,
|
|
onExpansionChanged: (context) {
|
|
LeftbarObserver.notifyAll(widget.title);
|
|
onChangeExpansion(context);
|
|
},
|
|
trailing: RotationTransition(
|
|
turns: _iconTurns,
|
|
child: Icon(
|
|
LucideIcons.chevron_down,
|
|
size: 18,
|
|
color: leftBarTheme.onBackground,
|
|
),
|
|
),
|
|
iconColor: leftBarTheme.activeItemColor,
|
|
childrenPadding: MySpacing.x(12),
|
|
title: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
widget.iconData,
|
|
size: 20,
|
|
color: isHover || isActive ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
|
),
|
|
MySpacing.width(18),
|
|
Expanded(
|
|
child: MyText.labelLarge(
|
|
widget.title,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
textAlign: TextAlign.start,
|
|
color: isHover || isActive ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
collapsedBackgroundColor: Colors.transparent,
|
|
shape: RoundedRectangleBorder(
|
|
side: BorderSide(color: Colors.transparent),
|
|
),
|
|
backgroundColor: Colors.transparent,
|
|
children: widget.children),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
// LeftbarObserver.detachListener(widget.title);
|
|
}
|
|
}
|
|
|
|
class MenuItem extends StatefulWidget {
|
|
final IconData? iconData;
|
|
final String title;
|
|
final bool isCondensed;
|
|
final String? route;
|
|
|
|
const MenuItem({
|
|
super.key,
|
|
this.iconData,
|
|
required this.title,
|
|
this.isCondensed = false,
|
|
this.route,
|
|
});
|
|
|
|
@override
|
|
_MenuItemState createState() => _MenuItemState();
|
|
}
|
|
|
|
class _MenuItemState extends State<MenuItem> with UIMixin {
|
|
bool isHover = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
bool isActive = UrlService.getCurrentUrl() == widget.route;
|
|
return GestureDetector(
|
|
onTap: () {
|
|
if (widget.route != null) {
|
|
Get.toNamed(widget.route!);
|
|
}
|
|
},
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.click,
|
|
onHover: (event) {
|
|
setState(() {
|
|
isHover = true;
|
|
});
|
|
},
|
|
onExit: (event) {
|
|
setState(() {
|
|
isHover = false;
|
|
});
|
|
},
|
|
child: MyContainer.transparent(
|
|
margin: MySpacing.fromLTRB(4, 0, 8, 4),
|
|
color: isActive || isHover ? leftBarTheme.activeItemBackground : Colors.transparent,
|
|
width: MediaQuery.of(context).size.width,
|
|
padding: MySpacing.xy(18, 7),
|
|
child: MyText.bodySmall(
|
|
"${widget.isCondensed ? "" : "- "} ${widget.title}",
|
|
overflow: TextOverflow.clip,
|
|
maxLines: 1,
|
|
textAlign: TextAlign.left,
|
|
fontSize: 12.5,
|
|
color: isActive || isHover ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
|
fontWeight: isActive || isHover ? 600 : 500,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class NavigationItem extends StatefulWidget {
|
|
final IconData? iconData;
|
|
final String title;
|
|
final bool isCondensed;
|
|
final String? route;
|
|
|
|
const NavigationItem({super.key, this.iconData, required this.title, this.isCondensed = false, this.route});
|
|
|
|
@override
|
|
_NavigationItemState createState() => _NavigationItemState();
|
|
}
|
|
|
|
class _NavigationItemState extends State<NavigationItem> with UIMixin {
|
|
bool isHover = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
bool isActive = UrlService.getCurrentUrl() == widget.route;
|
|
return GestureDetector(
|
|
onTap: () {
|
|
if (widget.route != null) {
|
|
Get.toNamed(widget.route!);
|
|
}
|
|
},
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.click,
|
|
onHover: (event) {
|
|
setState(() {
|
|
isHover = true;
|
|
});
|
|
},
|
|
onExit: (event) {
|
|
setState(() {
|
|
isHover = false;
|
|
});
|
|
},
|
|
child: MyContainer.transparent(
|
|
margin: MySpacing.fromLTRB(16, 0, 16, 8),
|
|
color: isActive || isHover ? leftBarTheme.activeItemBackground : Colors.transparent,
|
|
padding: MySpacing.xy(8, 8),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
if (widget.iconData != null)
|
|
Center(
|
|
child: Icon(
|
|
widget.iconData,
|
|
color: (isHover || isActive) ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
|
size: 20,
|
|
),
|
|
),
|
|
if (!widget.isCondensed)
|
|
Flexible(
|
|
fit: FlexFit.loose,
|
|
child: MySpacing.width(16),
|
|
),
|
|
if (!widget.isCondensed)
|
|
Expanded(
|
|
flex: 3,
|
|
child: MyText.labelLarge(
|
|
widget.title,
|
|
overflow: TextOverflow.clip,
|
|
maxLines: 1,
|
|
color: isActive || isHover ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|