150 lines
4.7 KiB
Dart
150 lines
4.7 KiB
Dart
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:go_router/go_router.dart';
|
||
|
|
import '../../models/nav_item.dart';
|
||
|
|
|
||
|
|
class NavItemTile extends StatelessWidget {
|
||
|
|
final NavItem item;
|
||
|
|
final String currentRoute;
|
||
|
|
|
||
|
|
const NavItemTile({
|
||
|
|
super.key,
|
||
|
|
required this.item,
|
||
|
|
required this.currentRoute,
|
||
|
|
});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
if (item.hasChildren) {
|
||
|
|
return _GroupTile(item: item, currentRoute: currentRoute);
|
||
|
|
}
|
||
|
|
return _LeafTile(item: item, currentRoute: currentRoute);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Leaf (item sin hijos) ──────────────────────────────────────────────────
|
||
|
|
|
||
|
|
class _LeafTile extends StatelessWidget {
|
||
|
|
final NavItem item;
|
||
|
|
final String currentRoute;
|
||
|
|
|
||
|
|
const _LeafTile({required this.item, required this.currentRoute});
|
||
|
|
|
||
|
|
bool get _isActive =>
|
||
|
|
item.route != null && currentRoute == item.route;
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
final cs = Theme.of(context).colorScheme;
|
||
|
|
final tt = Theme.of(context).textTheme;
|
||
|
|
|
||
|
|
return Padding(
|
||
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1),
|
||
|
|
child: Material(
|
||
|
|
color: _isActive ? cs.secondaryContainer : Colors.transparent,
|
||
|
|
borderRadius: BorderRadius.circular(12),
|
||
|
|
child: InkWell(
|
||
|
|
borderRadius: BorderRadius.circular(12),
|
||
|
|
onTap: () => context.go(item.route!),
|
||
|
|
child: Padding(
|
||
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||
|
|
child: Row(
|
||
|
|
children: [
|
||
|
|
Icon(
|
||
|
|
item.icon,
|
||
|
|
size: 20,
|
||
|
|
color: _isActive ? cs.onSecondaryContainer : cs.onSurfaceVariant,
|
||
|
|
),
|
||
|
|
const SizedBox(width: 12),
|
||
|
|
Expanded(
|
||
|
|
child: Text(
|
||
|
|
item.label,
|
||
|
|
style: tt.bodyMedium?.copyWith(
|
||
|
|
color: _isActive ? cs.onSecondaryContainer : cs.onSurface,
|
||
|
|
fontWeight: _isActive ? FontWeight.w600 : FontWeight.normal,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Group (item con hijos, expansible) ────────────────────────────────────
|
||
|
|
|
||
|
|
class _GroupTile extends StatefulWidget {
|
||
|
|
final NavItem item;
|
||
|
|
final String currentRoute;
|
||
|
|
|
||
|
|
const _GroupTile({required this.item, required this.currentRoute});
|
||
|
|
|
||
|
|
@override
|
||
|
|
State<_GroupTile> createState() => _GroupTileState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _GroupTileState extends State<_GroupTile> {
|
||
|
|
late bool _expanded;
|
||
|
|
|
||
|
|
@override
|
||
|
|
void initState() {
|
||
|
|
super.initState();
|
||
|
|
_expanded = widget.item.isActiveOrAncestor(widget.currentRoute);
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
void didUpdateWidget(_GroupTile old) {
|
||
|
|
super.didUpdateWidget(old);
|
||
|
|
if (old.currentRoute != widget.currentRoute) {
|
||
|
|
if (widget.item.isActiveOrAncestor(widget.currentRoute)) {
|
||
|
|
_expanded = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
final cs = Theme.of(context).colorScheme;
|
||
|
|
final tt = Theme.of(context).textTheme;
|
||
|
|
final isAncestor = widget.item.isActiveOrAncestor(widget.currentRoute);
|
||
|
|
|
||
|
|
return Padding(
|
||
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1),
|
||
|
|
child: Theme(
|
||
|
|
// Quita el separador por defecto del ExpansionTile
|
||
|
|
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||
|
|
child: ExpansionTile(
|
||
|
|
initiallyExpanded: _expanded,
|
||
|
|
onExpansionChanged: (v) => setState(() => _expanded = v),
|
||
|
|
tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 0),
|
||
|
|
childrenPadding: const EdgeInsets.only(left: 16),
|
||
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
|
|
collapsedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
|
|
backgroundColor: isAncestor ? cs.surfaceContainerHigh : Colors.transparent,
|
||
|
|
collapsedBackgroundColor: Colors.transparent,
|
||
|
|
leading: Icon(
|
||
|
|
widget.item.icon,
|
||
|
|
size: 20,
|
||
|
|
color: isAncestor ? cs.primary : cs.onSurfaceVariant,
|
||
|
|
),
|
||
|
|
title: Text(
|
||
|
|
widget.item.label,
|
||
|
|
style: tt.bodyMedium?.copyWith(
|
||
|
|
color: isAncestor ? cs.primary : cs.onSurface,
|
||
|
|
fontWeight: isAncestor ? FontWeight.w600 : FontWeight.normal,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
children: widget.item.children
|
||
|
|
.map((child) => NavItemTile(
|
||
|
|
item: child,
|
||
|
|
currentRoute: widget.currentRoute,
|
||
|
|
))
|
||
|
|
.toList(),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|