books
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
export 'toly_ui/toly_ui.dart';
|
||||
export 'windows/window_buttons.dart';
|
||||
export 'windows/drag_to_move_area.dart';
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ColorsPanel extends StatelessWidget {
|
||||
final List<Color> colors;
|
||||
final ValueChanged<Color> onSelect;
|
||||
const ColorsPanel({super.key, required this.colors, required this.onSelect});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: colors
|
||||
.asMap()
|
||||
.keys
|
||||
.map((int index) => GestureDetector(
|
||||
onTap: () => onSelect(colors[index]),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: colors[index],
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(
|
||||
'$index',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class HoverIconButton extends StatefulWidget {
|
||||
final VoidCallback? onPressed;
|
||||
final IconData icon;
|
||||
final double size;
|
||||
final Color? hoverColor;
|
||||
final Color? defaultColor;
|
||||
final MouseCursor cursor;
|
||||
|
||||
const HoverIconButton({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.icon,
|
||||
this.hoverColor,
|
||||
this.size = 24,
|
||||
this.defaultColor,
|
||||
this.cursor = SystemMouseCursors.click,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HoverIconButton> createState() => _HoverIconButtonState();
|
||||
}
|
||||
|
||||
class _HoverIconButtonState extends State<HoverIconButton> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color? color = (_hover)
|
||||
? widget.hoverColor ?? Theme.of(context).primaryColor
|
||||
: (widget.defaultColor ?? null);
|
||||
|
||||
return MouseRegion(
|
||||
cursor: widget.cursor,
|
||||
onEnter: _onEnter,
|
||||
onExit: _onExit,
|
||||
child: GestureDetector(
|
||||
onTap: widget.onPressed,
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
size: widget.size,
|
||||
color: color,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void _onEnter(PointerEnterEvent event) {
|
||||
setState(() {
|
||||
_hover = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _onExit(PointerExitEvent event) {
|
||||
setState(() {
|
||||
_hover = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TolyTitle extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Color? lineColor;
|
||||
final Color? color;
|
||||
const TolyTitle({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.lineColor,
|
||||
this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: TitleDecoration(
|
||||
color,lineColor,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TitleDecoration extends Decoration {
|
||||
final Color? lineColor;
|
||||
final Color? color;
|
||||
|
||||
const TitleDecoration( this.color,this.lineColor,);
|
||||
|
||||
@override
|
||||
BoxPainter createBoxPainter([VoidCallback? onChanged]) =>
|
||||
TitlePainter(color: color, lineColor: lineColor);
|
||||
}
|
||||
|
||||
class TitlePainter extends BoxPainter {
|
||||
final Color? color;
|
||||
final Color? lineColor;
|
||||
|
||||
const TitlePainter({this.color, this.lineColor});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
|
||||
canvas.save();
|
||||
canvas.translate(offset.dx, offset.dy);
|
||||
Size size = configuration.size ?? Size.zero;
|
||||
final Paint paint = Paint()
|
||||
// ..style = PaintingStyle.stroke
|
||||
..color = color??Colors.transparent
|
||||
..strokeWidth = 1;
|
||||
|
||||
|
||||
final Rect zone = Rect.fromCenter(
|
||||
center: Offset(size.width / 2, size.height / 2),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
);
|
||||
|
||||
canvas.drawRect(zone, paint);
|
||||
final Paint paint2 = Paint()
|
||||
..strokeWidth = 1
|
||||
// ..style = PaintingStyle.stroke
|
||||
// ..color = const Color(0xffFFFAA7);
|
||||
..color = lineColor??Colors.transparent;
|
||||
|
||||
const double start = 4;
|
||||
canvas.drawLine(const Offset(0, start), Offset(size.width, start), paint2);
|
||||
double end = size.height - 4;
|
||||
canvas.drawLine(Offset(0, end), Offset(size.width, end), paint2);
|
||||
|
||||
canvas.drawCircle(Offset(10,size.height/2), 4, paint2);
|
||||
|
||||
//
|
||||
// canvas.translate(
|
||||
// offset.dx + (configuration.size?.width??0) / 2,
|
||||
// offset.dy + (configuration.size?.height??0) / 2,
|
||||
// );
|
||||
//
|
||||
// final Rect zone = Rect.fromCenter(
|
||||
// center: Offset.zero,
|
||||
// width: configuration.size.width,
|
||||
// height: configuration.size.height,
|
||||
// );
|
||||
//
|
||||
// path.addRRect(RRect.fromRectAndRadius(
|
||||
// zone,
|
||||
// Radius.circular(20),
|
||||
// ));
|
||||
//
|
||||
// const DashPainter(span: 4, step: 9).paint(canvas, path, paint);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class MenuMeta {
|
||||
// 标签
|
||||
final String label;
|
||||
|
||||
final String? path;
|
||||
|
||||
// 图标数据
|
||||
final IconData icon;
|
||||
|
||||
// 图标颜色
|
||||
final Color? color;
|
||||
|
||||
const MenuMeta({
|
||||
required this.label,
|
||||
this.path,
|
||||
required this.icon,
|
||||
this.color,
|
||||
});
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/src/gestures/events.dart';
|
||||
|
||||
class TolyBreadcrumb extends StatelessWidget {
|
||||
final List<BreadcrumbItem> items;
|
||||
final ValueChanged<BreadcrumbItem>? onTapItem;
|
||||
|
||||
const TolyBreadcrumb({super.key, required this.items, this.onTapItem});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> children = [];
|
||||
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
children.add(TolyBreadcrumbItem(
|
||||
item: items[i], onTapItem: onTapItem,
|
||||
));
|
||||
if (i != items.length - 1) {
|
||||
children.add(Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: Text(
|
||||
'/',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return Wrap(
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TolyBreadcrumbItem extends StatefulWidget {
|
||||
final BreadcrumbItem item;
|
||||
final ValueChanged<BreadcrumbItem>? onTapItem;
|
||||
const TolyBreadcrumbItem({super.key, required this.item, required this.onTapItem});
|
||||
|
||||
@override
|
||||
State<TolyBreadcrumbItem> createState() => _TolyBreadcrumbItemState();
|
||||
}
|
||||
|
||||
class _TolyBreadcrumbItemState extends State<TolyBreadcrumbItem> {
|
||||
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool hasTarget = (widget.item.to != null);
|
||||
Color? color = (_hover&&hasTarget)?Colors.blue:null;
|
||||
MouseCursor cursor = hasTarget?SystemMouseCursors.click:SystemMouseCursors.basic;
|
||||
|
||||
if(widget.item.active) {
|
||||
color = null;
|
||||
cursor = SystemMouseCursors.basic;
|
||||
}
|
||||
|
||||
TextStyle style = TextStyle(
|
||||
fontWeight: hasTarget ? FontWeight.bold : null,
|
||||
color: color
|
||||
);
|
||||
|
||||
return MouseRegion(
|
||||
cursor: cursor,
|
||||
onEnter: _onEnter,
|
||||
onExit: _onExit,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if(!widget.item.active){
|
||||
widget.onTapItem?.call(widget.item);
|
||||
}
|
||||
},
|
||||
child: Text(widget.item.label, style: style)),
|
||||
);
|
||||
}
|
||||
|
||||
void _onEnter(PointerEnterEvent event) {
|
||||
setState(() {
|
||||
_hover = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _onExit(PointerExitEvent event) {
|
||||
setState(() {
|
||||
_hover = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class BreadcrumbItem {
|
||||
final String? to;
|
||||
final String label;
|
||||
final bool active;
|
||||
|
||||
BreadcrumbItem( {this.to, required this.label,this.active=false,});
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'menu_meta.dart';
|
||||
|
||||
class TolyNavigationRail extends StatelessWidget {
|
||||
final ValueChanged<int> onDestinationSelected;
|
||||
final Color backgroundColor;
|
||||
final int? selectedIndex;
|
||||
final Widget? leading;
|
||||
final Widget? tail;
|
||||
final List<MenuMeta> items;
|
||||
const TolyNavigationRail({
|
||||
Key? key,
|
||||
required this.onDestinationSelected,
|
||||
required this.selectedIndex,
|
||||
required this.items,
|
||||
this.leading,
|
||||
this.tail,
|
||||
required this.backgroundColor,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 64,
|
||||
color: backgroundColor,
|
||||
child: Column(
|
||||
children: [
|
||||
if(leading!=null) leading!,
|
||||
Expanded(
|
||||
child: LeftNavigationMenu(
|
||||
items: items,
|
||||
selectedIndex: selectedIndex,
|
||||
onTapItem: onDestinationSelected,
|
||||
)),
|
||||
if(tail!=null) tail!,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LeftNavigationMenu extends StatelessWidget {
|
||||
final List<MenuMeta> items;
|
||||
final ValueChanged<int> onTapItem;
|
||||
|
||||
final int? selectedIndex;
|
||||
|
||||
const LeftNavigationMenu(
|
||||
{Key? key,
|
||||
required this.items,
|
||||
required this.selectedIndex,
|
||||
required this.onTapItem})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: items.asMap().keys.map((int index) {
|
||||
return LeftNavigationBarItemWidget(
|
||||
item: items[index],
|
||||
selected: index == selectedIndex,
|
||||
onTap: () {
|
||||
onTapItem.call(index);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LeftNavigationBarItemWidget extends StatefulWidget {
|
||||
final MenuMeta item;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const LeftNavigationBarItemWidget(
|
||||
{Key? key,
|
||||
required this.item,
|
||||
required this.selected,
|
||||
required this.onTap})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<LeftNavigationBarItemWidget> createState() =>
|
||||
_LeftNavigationBarItemWidgetState();
|
||||
}
|
||||
|
||||
class _LeftNavigationBarItemWidgetState
|
||||
extends State<LeftNavigationBarItemWidget> {
|
||||
bool _hover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color? bgColor;
|
||||
Color iconColor;
|
||||
if (widget.selected) {
|
||||
bgColor = Colors.white.withOpacity(0.2);
|
||||
iconColor = Colors.white;
|
||||
} else {
|
||||
bgColor = _hover ? Colors.white.withOpacity(0.1) : null;
|
||||
iconColor = Colors.white.withOpacity(0.8);
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: widget.selected
|
||||
? null
|
||||
: () {
|
||||
widget.onTap();
|
||||
setState(() {
|
||||
_hover = false;
|
||||
});
|
||||
},
|
||||
onHover: widget.selected
|
||||
? null
|
||||
: (v) {
|
||||
setState(() {
|
||||
_hover = v;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
margin: EdgeInsets.only(bottom: 10),
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor, borderRadius: BorderRadius.circular(8)),
|
||||
child: Wrap(
|
||||
direction: Axis.vertical,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: 2,
|
||||
children: [
|
||||
Icon(
|
||||
widget.item.icon,
|
||||
color: iconColor,
|
||||
),
|
||||
Text(
|
||||
widget.item.label,
|
||||
style: TextStyle(color: iconColor, fontSize: 12),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef OnDropSelected = void Function(int index);
|
||||
|
||||
class DropSelectableWidget extends StatefulWidget {
|
||||
final List<String> data;
|
||||
final OnDropSelected? onDropSelected;
|
||||
final Color disableColor;
|
||||
final double iconSize;
|
||||
final double height;
|
||||
final double width;
|
||||
final double fontSize;
|
||||
final String value;
|
||||
|
||||
const DropSelectableWidget(
|
||||
{Key? key,
|
||||
this.data = const [],
|
||||
this.onDropSelected,
|
||||
this.disableColor = Colors.black,
|
||||
this.iconSize = 24,
|
||||
required this.value,
|
||||
this.height = 30,
|
||||
this.width = 200,
|
||||
this.fontSize = 14,
|
||||
})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_DropSelectableWidgetState createState() => _DropSelectableWidgetState();
|
||||
}
|
||||
|
||||
class _DropSelectableWidgetState extends State<DropSelectableWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late FocusNode _node;
|
||||
bool _focused = false;
|
||||
late FocusAttachment _nodeAttachment;
|
||||
OverlayEntry? _overlayEntry;
|
||||
late AnimationController _ctrl;
|
||||
late Animation<double> animation;
|
||||
final LayerLink layerLink = LayerLink();
|
||||
|
||||
// int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_ctrl = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
|
||||
animation = Tween<double>(begin: 0, end: pi).animate(_ctrl);
|
||||
_node = FocusNode()
|
||||
..addListener(() {
|
||||
if (_node.hasFocus != _focused) {
|
||||
if (!_focused) {
|
||||
_ctrl.forward();
|
||||
_showOverlay();
|
||||
} else {
|
||||
_hideOverlay();
|
||||
_ctrl.reverse();
|
||||
}
|
||||
setState(() {
|
||||
_focused = _node.hasFocus;
|
||||
});
|
||||
}
|
||||
});
|
||||
_nodeAttachment = _node.attach(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_node.dispose();
|
||||
_ctrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_nodeAttachment.reparent();
|
||||
return TapRegion(
|
||||
groupId: 'selector',
|
||||
onTapOutside: (_){
|
||||
_node.unfocus();
|
||||
},
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (_focused) {
|
||||
_node.unfocus();
|
||||
} else {
|
||||
_node.requestFocus();
|
||||
}
|
||||
},
|
||||
child: CompositedTransformTarget(
|
||||
link: layerLink,
|
||||
child: buildTarget(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showOverlay() {
|
||||
_overlayEntry = _createOverlayEntry();
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
}
|
||||
|
||||
void _hideOverlay() {
|
||||
_overlayEntry?.remove();
|
||||
}
|
||||
|
||||
Widget buildTarget() {
|
||||
return Container(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: _focused ? Colors.blue : widget.disableColor,
|
||||
)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text( widget.value ,style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: widget.fontSize
|
||||
),),
|
||||
AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (_, child) => Transform.rotate(
|
||||
angle: animation.value,
|
||||
child: child,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
size: widget.iconSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
OverlayEntry _createOverlayEntry() => OverlayEntry(
|
||||
builder: (BuildContext context) => UnconstrainedBox(
|
||||
child: CompositedTransformFollower(
|
||||
link: layerLink,
|
||||
targetAnchor: Alignment.bottomCenter,
|
||||
followerAnchor: Alignment.topCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Material(
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide.none,
|
||||
borderRadius: BorderRadius.all(Radius.circular(5))),
|
||||
elevation: 1,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Container(
|
||||
height: 200,
|
||||
// alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xffDAE3FF),
|
||||
),
|
||||
// padding: const EdgeInsets.only(top: 5),
|
||||
width: widget.width,
|
||||
child: CupertinoScrollbar(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
// shrinkWrap: true,
|
||||
itemCount: widget.data.length,
|
||||
itemBuilder: _buildItem),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildItem(BuildContext context, int index) {
|
||||
|
||||
bool active = widget.data[index] == widget.value;
|
||||
return TapRegion(
|
||||
groupId: 'selector',
|
||||
child: Material(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (!active) widget.onDropSelected?.call(index);
|
||||
_overlayEntry?.markNeedsBuild();
|
||||
_node.unfocus();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: active ? Colors.blue.withOpacity(0.2) : Colors.transparent,
|
||||
child: Text(widget.data[index],style: TextStyle(fontSize: widget.fontSize),)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'toly_pop_menu.dart';
|
||||
|
||||
class PopPanel<T> extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Size size;
|
||||
final Offset offset;
|
||||
|
||||
/// Builds the context menu.
|
||||
final Widget panel;
|
||||
|
||||
const PopPanel({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.panel,
|
||||
this.offset = Offset.zero,
|
||||
this.size = const Size(250, 0),
|
||||
});
|
||||
|
||||
@override
|
||||
State<PopPanel> createState() => _PopPanelState();
|
||||
}
|
||||
|
||||
class _PopPanelState<T> extends State<PopPanel<T>> {
|
||||
final ContextMenuController _contextMenuController = ContextMenuController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: _onTap,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _show() {
|
||||
TolyPopupMenuEntry<T> item = CustomTolyMenuItem(
|
||||
widget.panel,
|
||||
size: widget.size,
|
||||
);
|
||||
|
||||
final RenderBox button = context.findRenderObject()! as RenderBox;
|
||||
final RenderBox overlay =
|
||||
Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
|
||||
final Offset offset = Offset(button.size.width / 2, button.size.height)+widget.offset;
|
||||
final RelativeRect position = RelativeRect.fromRect(
|
||||
Rect.fromPoints(
|
||||
button.localToGlobal(offset, ancestor: overlay),
|
||||
button.localToGlobal(button.size.bottomRight(Offset.zero) + offset,
|
||||
ancestor: overlay),
|
||||
),
|
||||
Offset.zero & overlay.size,
|
||||
);
|
||||
showTolyMenu<T?>(
|
||||
elevation: 0,
|
||||
color: Colors.transparent,
|
||||
context: context,
|
||||
items: [item],
|
||||
position: position,
|
||||
).then<void>((T? newValue) {
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
if (newValue == null) {
|
||||
// widget.onCanceled?.call();
|
||||
return null;
|
||||
}
|
||||
// widget.onSelected?.call(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
void _onTap() {
|
||||
_show();
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTolyMenuItem<T> extends TolyPopupMenuEntry<T> {
|
||||
final Size size;
|
||||
final Widget content;
|
||||
|
||||
const CustomTolyMenuItem(this.content, {super.key, required this.size});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _CustomTolyMenuItemState();
|
||||
|
||||
@override
|
||||
// TODO: implement height
|
||||
double get height => kMinInteractiveDimension;
|
||||
|
||||
@override
|
||||
bool represents(value) => true;
|
||||
}
|
||||
|
||||
class _CustomTolyMenuItemState<T> extends State<CustomTolyMenuItem> {
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CustomTolyMenuItem oldWidget) {
|
||||
print('============_CustomTolyMenuItemState#didUpdateWidget');
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: widget.size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 6)
|
||||
]),
|
||||
// padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
child: widget.content,
|
||||
// color: Colors.lightBlueAccent,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A builder that includes an Offset to draw the context menu at.
|
||||
typedef ContextMenuBuilder = Widget Function(BuildContext context, Offset offset);
|
||||
|
||||
|
||||
class Popover extends StatefulWidget {
|
||||
final Widget child;
|
||||
/// Builds the context menu.
|
||||
final ContextMenuBuilder contextMenuBuilder;
|
||||
|
||||
const Popover({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.contextMenuBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
State<Popover> createState() => _PopoverState();
|
||||
}
|
||||
|
||||
class _PopoverState extends State<Popover> {
|
||||
|
||||
final ContextMenuController _contextMenuController = ContextMenuController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: _onTap,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _show(Offset position) {
|
||||
_contextMenuController.show(
|
||||
context: context,
|
||||
contextMenuBuilder: (BuildContext context) {
|
||||
return widget.contextMenuBuilder(context, position);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final double width = 200;
|
||||
|
||||
void _onTap() {
|
||||
final RenderBox button = context.findRenderObject()! as RenderBox;
|
||||
final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
|
||||
final Offset offset = Offset(button.size.width / 2, button.size.height);
|
||||
Offset boxOffset = button.localToGlobal(offset, ancestor: overlay);
|
||||
_show(boxOffset.translate(-width, 0));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
|
||||
export 'navigation/menu_meta.dart';
|
||||
export 'navigation/toly_breadcrumb.dart';
|
||||
export 'navigation/toly_navigation_rail.dart';
|
||||
export 'popable/drop_selectable_widget.dart';
|
||||
export 'popable/popover.dart';
|
||||
export 'popable/pop_menu.dart';
|
||||
@@ -1,24 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class DragToMoveWrap extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const DragToMoveWrap({
|
||||
Key? key,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if(kIsWeb) return child;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPanStart: (details) {
|
||||
windowManager.startDragging();
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class WindowButtons extends StatefulWidget {
|
||||
final List<Widget>? actions;
|
||||
const WindowButtons({Key? key, this.actions}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<WindowButtons> createState() => _WindowButtonsState();
|
||||
}
|
||||
|
||||
class _WindowButtonsState extends State<WindowButtons> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Brightness brightness = Theme.of(context).brightness;
|
||||
return Align(
|
||||
alignment:Alignment.topRight,child: Wrap(
|
||||
spacing: 5,
|
||||
children: [
|
||||
if(widget.actions!=null)
|
||||
...widget.actions!,
|
||||
SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: WindowCaptionButton.minimize(
|
||||
brightness:brightness,
|
||||
onPressed: () async {
|
||||
bool isMinimized = await windowManager.isMinimized();
|
||||
if (isMinimized) {
|
||||
windowManager.restore();
|
||||
} else {
|
||||
windowManager.minimize();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: FutureBuilder<bool>(
|
||||
future: windowManager.isMaximized(),
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.data == true) {
|
||||
return WindowCaptionButton.unmaximize(
|
||||
brightness: brightness,
|
||||
onPressed: () async{
|
||||
await windowManager.unmaximize();
|
||||
setState(() {
|
||||
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
return WindowCaptionButton.maximize(
|
||||
brightness: brightness,
|
||||
onPressed: () async{
|
||||
await windowManager.maximize();
|
||||
setState(() {
|
||||
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 30,
|
||||
width: 30,
|
||||
child: WindowCaptionButton.close(
|
||||
brightness: brightness,
|
||||
onPressed: () {
|
||||
windowManager.close();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'navigation/app_navigation.dart';
|
||||
import 'v12/app.dart';
|
||||
import 'navigation/router/menus/menu_scope/menu_scope.dart';
|
||||
import 'navigation/router/routers/app.dart';
|
||||
import 'navigation/transition/fade_page_transitions_builder.dart';
|
||||
import 'navigation/views/app_navigation.dart';
|
||||
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -16,6 +19,46 @@ void main() {
|
||||
}
|
||||
|
||||
|
||||
class TolyBooksApp extends StatelessWidget {
|
||||
final GoRouter _router = GoRouter(
|
||||
initialLocation: '/dashboard/view',
|
||||
routes: <RouteBase>[appRoute],
|
||||
onException: (BuildContext ctx, GoRouterState state, GoRouter router) {
|
||||
router.go('/404', extra: state.uri.toString());
|
||||
},
|
||||
);
|
||||
|
||||
late final MenuStore menuStore = MenuStore(
|
||||
activeMenu: '/dashboard/view',
|
||||
expandMenus: ['/dashboard'],
|
||||
goRouter: _router,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MenuScope(
|
||||
notifier: menuStore,
|
||||
child: MaterialApp.router(
|
||||
routerConfig: _router,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
fontFamily: "宋体",
|
||||
primarySwatch: Colors.blue,
|
||||
pageTransitionsTheme: const PageTransitionsTheme(builders: {
|
||||
TargetPlatform.android: ZoomPageTransitionsBuilder(),
|
||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.macOS: FadePageTransitionsBuilder(),
|
||||
TargetPlatform.windows: FadePageTransitionsBuilder(),
|
||||
TargetPlatform.linux: FadePageTransitionsBuilder(),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void setSize() async{
|
||||
if(kIsWeb||Platform.isAndroid||Platform.isIOS) return;
|
||||
await windowManager.ensureInitialized();
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:iroute/components/components.dart';
|
||||
import 'package:toly_menu/src/menu.dart';
|
||||
import 'package:toly_menu/src/model/menu_state.dart';
|
||||
import 'package:toly_menu/toly_menu.dart';
|
||||
|
||||
import '../v12/app/navigation/transition/fade_page_transitions_builder.dart';
|
||||
import 'router/menus/menu_tree.dart';
|
||||
import 'views/top_logo.dart';
|
||||
import 'views/top_bar.dart';
|
||||
import 'package:iroute/navigation/router/routers/app.dart';
|
||||
|
||||
class TolyBooksApp extends StatelessWidget {
|
||||
final GoRouter _router = GoRouter(
|
||||
initialLocation: '/dashboard/view',
|
||||
routes: <RouteBase>[appRoute],
|
||||
onException: (BuildContext ctx, GoRouterState state, GoRouter router) {
|
||||
router.go('/404', extra: state.uri.toString());
|
||||
},
|
||||
// redirect: _authRedirect
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
routerConfig: _router,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
fontFamily: "宋体",
|
||||
primarySwatch: Colors.blue,
|
||||
pageTransitionsTheme: const PageTransitionsTheme(builders: {
|
||||
TargetPlatform.android: ZoomPageTransitionsBuilder(),
|
||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.macOS: FadePageTransitionsBuilder(),
|
||||
TargetPlatform.windows: FadePageTransitionsBuilder(),
|
||||
TargetPlatform.linux: FadePageTransitionsBuilder(),
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BookAppNavigation extends StatefulWidget {
|
||||
final Widget content;
|
||||
const BookAppNavigation({super.key, required this.content});
|
||||
|
||||
@override
|
||||
State<BookAppNavigation> createState() => _BookAppNavigationState();
|
||||
}
|
||||
|
||||
class _BookAppNavigationState extends State<BookAppNavigation> {
|
||||
MenuState state = MenuState(
|
||||
expandMenus: ['/dashboard'],
|
||||
activeMenu: '/dashboard/view',
|
||||
items: rootMenu.children);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
DragToMoveWrap(
|
||||
child: Container(
|
||||
color: const Color(0xff001529),
|
||||
width: 210,
|
||||
child: Column(
|
||||
children: [
|
||||
TopLogo(),
|
||||
Expanded(child: TolyMenu(state: state, onSelect: _onSelect)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [AppTopBar(), Expanded(child: widget.content)],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSelect(MenuNode menu) {
|
||||
if (menu.isLeaf) {
|
||||
state = state.copyWith(activeMenu: menu.path);
|
||||
print(menu.path);
|
||||
// print(;
|
||||
context.go(menu.path);
|
||||
} else {
|
||||
List<String> menus = [];
|
||||
String path = menu.path.substring(1);
|
||||
List<String> parts = path.split('/');
|
||||
|
||||
if (parts.isNotEmpty) {
|
||||
String path = '';
|
||||
for (String part in parts) {
|
||||
path += '/$part';
|
||||
menus.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.expandMenus.contains(menu.path)) {
|
||||
menus.remove(menu.path);
|
||||
}
|
||||
|
||||
state = state.copyWith(expandMenus: menus);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import '../../../app/res/fx_icon.dart';
|
||||
|
||||
Map<String,dynamic> animaMenus = {
|
||||
'path' : '/anima',
|
||||
'label' : '动画-流光幻影',
|
||||
'icon': FxIcon.icon_anima,
|
||||
'children': [
|
||||
{
|
||||
'path' : '/chapter1',
|
||||
|
||||
32
lib/navigation/router/menus/dashboard.dart
Normal file
32
lib/navigation/router/menus/dashboard.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
import '../../../app/res/fx_icon.dart';
|
||||
|
||||
Map<String, dynamic> dashboard = {
|
||||
'path': '/dashboard',
|
||||
'label': '面板总览',
|
||||
'icon': FxIcon.dashboard,
|
||||
'children': [
|
||||
{
|
||||
'path': '/view',
|
||||
'label': '小册全集',
|
||||
},
|
||||
{
|
||||
'path': '/chat',
|
||||
'label': '读者交流',
|
||||
'children': [
|
||||
{
|
||||
'path': '/a',
|
||||
'label': '第一交流区',
|
||||
},
|
||||
{
|
||||
'path': '/b',
|
||||
'label': '第二交流区',
|
||||
},
|
||||
{
|
||||
'path': '/c',
|
||||
'label': '第三交流区',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/app/res/fx_icon.dart';
|
||||
|
||||
Map<String, dynamic> drawMenus = {
|
||||
'path': '/draw',
|
||||
'icon': FxIcon.icon_paint,
|
||||
'label': '绘制-妙笔生花',
|
||||
'children': [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import '../../../app/res/fx_icon.dart';
|
||||
|
||||
Map<String,dynamic> dreamMenus = {
|
||||
'path' : '/dream',
|
||||
'label' : '基础-梦始之地',
|
||||
'icon': FxIcon.icon_dream,
|
||||
'children': [
|
||||
{
|
||||
'path' : '/chapter1',
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import '../../../app/res/fx_icon.dart';
|
||||
|
||||
Map<String,dynamic> layoutMenus = {
|
||||
'path' : '/layout',
|
||||
'label' : '布局-薪火相传',
|
||||
'icon': FxIcon.icon_layout,
|
||||
|
||||
'children': [
|
||||
{
|
||||
'path' : '/chapter1',
|
||||
|
||||
9
lib/navigation/router/menus/menu_scope/menu_history.dart
Normal file
9
lib/navigation/router/menus/menu_scope/menu_history.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
class MenuHistory {
|
||||
final String menuLabel;
|
||||
final String menuPath;
|
||||
|
||||
MenuHistory({
|
||||
required this.menuLabel,
|
||||
required this.menuPath,
|
||||
});
|
||||
}
|
||||
200
lib/navigation/router/menus/menu_scope/menu_scope.dart
Normal file
200
lib/navigation/router/menus/menu_scope/menu_scope.dart
Normal file
@@ -0,0 +1,200 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:toly_menu/toly_menu.dart';
|
||||
|
||||
import '../menu_tree.dart';
|
||||
import 'menu_history.dart';
|
||||
|
||||
class MenuScope extends InheritedNotifier<MenuStore> {
|
||||
const MenuScope({super.key, required super.child, super.notifier});
|
||||
|
||||
static MenuStore of(BuildContext context) {
|
||||
return context.dependOnInheritedWidgetOfExactType<MenuScope>()!.notifier!;
|
||||
}
|
||||
|
||||
static MenuStore read(BuildContext context) {
|
||||
return context.getInheritedWidgetOfExactType<MenuScope>()!.notifier!;
|
||||
}
|
||||
}
|
||||
|
||||
class MenuStore with ChangeNotifier {
|
||||
late MenuState _state;
|
||||
MenuState get state => _state;
|
||||
final GoRouter goRouter;
|
||||
final List<MenuHistory> _history = [];
|
||||
List<MenuHistory> get history => _history;
|
||||
|
||||
MenuStore({
|
||||
List<String> expandMenus = const [],
|
||||
required this.goRouter,
|
||||
required String activeMenu,
|
||||
}) {
|
||||
_state = MenuState(
|
||||
expandMenus: expandMenus,
|
||||
activeMenu: activeMenu,
|
||||
items: rootMenu.children,
|
||||
);
|
||||
|
||||
_history.add(MenuHistory(
|
||||
menuLabel: pathName(activeMenu) ?? activeMenu,
|
||||
menuPath: activeMenu,
|
||||
));
|
||||
goRouter.routerDelegate.addListener(_onRouterChange);
|
||||
}
|
||||
|
||||
MenuNode? get currentNode => queryMenuNodeByPath(
|
||||
MenuNode(
|
||||
path: '',
|
||||
label: '',
|
||||
deep: -1,
|
||||
children: _state.items,
|
||||
),
|
||||
_state.activeMenu);
|
||||
|
||||
String? pathName(String path) => queryMenuNodeByPath(
|
||||
MenuNode(
|
||||
path: '',
|
||||
label: '',
|
||||
deep: -1,
|
||||
children: _state.items,
|
||||
),
|
||||
path)
|
||||
?.label;
|
||||
|
||||
void selectMenuPath(String path) {
|
||||
print("======================selectMenuPath:$path======${_shouldAddHistory}==============");
|
||||
MenuNode root = MenuNode(
|
||||
path: '',
|
||||
label: '',
|
||||
deep: -1,
|
||||
children: _state.items,
|
||||
);
|
||||
List<MenuNode> result = findNodes(root, Uri.parse(path), 0, '/', []);
|
||||
if (result.isNotEmpty) {
|
||||
List<String> expandMenus = [];
|
||||
if (result.length > 1) {
|
||||
expandMenus.addAll(
|
||||
result.sublist(0, result.length - 1).map((e) => e.path).toList());
|
||||
}
|
||||
if(_shouldAddHistory){
|
||||
_history.add(MenuHistory(
|
||||
menuLabel: result.last.label,
|
||||
menuPath: result.last.path,
|
||||
));
|
||||
}else{
|
||||
_shouldAddHistory = true;
|
||||
}
|
||||
|
||||
_state = state.copyWith(
|
||||
activeMenu: result.last.path, expandMenus: expandMenus);
|
||||
}
|
||||
}
|
||||
|
||||
bool _shouldAddHistory = true;
|
||||
void selectHistory(String path) {
|
||||
_shouldAddHistory = false;
|
||||
goRouter.go(path);
|
||||
}
|
||||
|
||||
void closeHistory(MenuHistory history) {
|
||||
int index = _history.indexOf(history);
|
||||
if(_history.length==1){
|
||||
return;
|
||||
}
|
||||
if(state.activeMenu!=history.menuPath){
|
||||
_history.remove(history);
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
MenuHistory nextActiveHistory;
|
||||
if(index==_history.length-1){
|
||||
///
|
||||
nextActiveHistory = _history[_history.length-2];
|
||||
}else{
|
||||
nextActiveHistory = _history[index+1];
|
||||
}
|
||||
_history.remove(history);
|
||||
selectHistory(nextActiveHistory.menuPath);
|
||||
}
|
||||
|
||||
List<MenuNode> findNodes(
|
||||
MenuNode node,
|
||||
Uri uri,
|
||||
int deep,
|
||||
String prefix,
|
||||
List<MenuNode> result,
|
||||
) {
|
||||
List<String> parts = uri.pathSegments;
|
||||
if (deep > parts.length - 1) {
|
||||
return result;
|
||||
}
|
||||
String target = parts[deep];
|
||||
if (node.children.isNotEmpty) {
|
||||
target = prefix + target;
|
||||
List<MenuNode> nodes =
|
||||
node.children.where((e) => e.path == target).toList();
|
||||
bool match = nodes.isNotEmpty;
|
||||
if (match) {
|
||||
MenuNode matched = nodes.first;
|
||||
result.add(matched);
|
||||
String nextPrefix = '${matched.path}/';
|
||||
findNodes(matched, uri, ++deep, nextPrefix, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MenuNode? queryMenuNodeByPath(MenuNode node, String path) {
|
||||
if (node.path == path) {
|
||||
return node;
|
||||
}
|
||||
if (node.children.isNotEmpty) {
|
||||
for (int i = 0; i < node.children.length; i++) {
|
||||
MenuNode? result = queryMenuNodeByPath(node.children[i], path);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void select(MenuNode menu) {
|
||||
if (menu.isLeaf) {
|
||||
_state = state.copyWith(activeMenu: menu.path);
|
||||
goRouter.go(menu.path);
|
||||
_history.add(MenuHistory(
|
||||
menuLabel: menu.label,
|
||||
menuPath: menu.path,
|
||||
));
|
||||
// notifyListeners();
|
||||
} else {
|
||||
List<String> menus = [];
|
||||
String path = menu.path.substring(1);
|
||||
List<String> parts = path.split('/');
|
||||
|
||||
if (parts.isNotEmpty) {
|
||||
String path = '';
|
||||
for (String part in parts) {
|
||||
path += '/$part';
|
||||
menus.add(path);
|
||||
}
|
||||
}
|
||||
if (state.expandMenus.contains(menu.path)) {
|
||||
menus.remove(menu.path);
|
||||
}
|
||||
|
||||
_state = state.copyWith(expandMenus: menus);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void _onRouterChange() {
|
||||
String path =
|
||||
goRouter.routerDelegate.currentConfiguration.last.matchedLocation;
|
||||
if (path != state.activeMenu) {
|
||||
selectMenuPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:toly_menu/toly_menu.dart';
|
||||
|
||||
import 'anima.dart';
|
||||
import 'dashboard.dart';
|
||||
import 'draw.dart';
|
||||
import 'dream.dart';
|
||||
import 'layout.dart';
|
||||
@@ -23,40 +25,13 @@ Map<String, dynamic> root = {
|
||||
]
|
||||
};
|
||||
|
||||
Map<String, dynamic> dashboard = {
|
||||
'path': '/dashboard',
|
||||
'label': '面板总览',
|
||||
'children': [
|
||||
{
|
||||
'path': '/view',
|
||||
'label': '小册全集',
|
||||
},
|
||||
{
|
||||
'path': '/chat',
|
||||
'label': '读者交流',
|
||||
'children': [
|
||||
{
|
||||
'path': '/a',
|
||||
'label': '第一交流区',
|
||||
},
|
||||
{
|
||||
'path': '/b',
|
||||
'label': '第二交流区',
|
||||
},
|
||||
{
|
||||
'path': '/c',
|
||||
'label': '第三交流区',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
MenuNode get rootMenu => parser(root, -1, '');
|
||||
|
||||
MenuNode parser(Map<String, dynamic> data, int deep, String prefix) {
|
||||
String path = data['path'];
|
||||
String label = data['label'];
|
||||
IconData? icon = data['icon'];
|
||||
List<Map<String, dynamic>>? childrenMap = data['children'];
|
||||
List<MenuNode> children = [];
|
||||
if (childrenMap != null && childrenMap.isNotEmpty) {
|
||||
@@ -66,6 +41,7 @@ MenuNode parser(Map<String, dynamic> data, int deep, String prefix) {
|
||||
}
|
||||
}
|
||||
return MenuNode(
|
||||
icon: icon,
|
||||
path: prefix + path,
|
||||
label: label,
|
||||
deep: deep,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import '../../../app/res/fx_icon.dart';
|
||||
|
||||
Map<String,dynamic> renderMenus = {
|
||||
'path' : '/render',
|
||||
'label' : '渲染-聚沙成塔',
|
||||
'icon': FxIcon.icon_sun,
|
||||
|
||||
'children': [
|
||||
{
|
||||
'path' : '/chapter1',
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import '../../../app/res/fx_icon.dart';
|
||||
|
||||
Map<String,dynamic> scrollMenus = {
|
||||
'path' : '/scroll',
|
||||
'label' : '滑动-珠联璧合',
|
||||
'icon': FxIcon.icon_scroll,
|
||||
|
||||
'children': [
|
||||
{
|
||||
'path' : '/chapter1',
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import '../../../app/res/fx_icon.dart';
|
||||
|
||||
Map<String,dynamic> touchMenus = {
|
||||
'path' : '/touch',
|
||||
'label' : '手势-执掌天下',
|
||||
'icon': FxIcon.fill_shoushi,
|
||||
'children': [
|
||||
{
|
||||
'path' : '/chapter1',
|
||||
|
||||
36
lib/navigation/router/routers/anima.dart
Normal file
36
lib/navigation/router/routers/anima.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:idraw/idraw.dart';
|
||||
import 'package:iroute/pages/empty/empty_panel.dart';
|
||||
|
||||
final RouteBase animaRouters = GoRoute(
|
||||
path: '/anima/chapter:index',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
String? index = state.pathParameters['index'];
|
||||
switch(index){
|
||||
case '1':
|
||||
return const P01Page();
|
||||
case '2':
|
||||
return const P01Page();
|
||||
case '3':
|
||||
return const P03Page();
|
||||
case '4':
|
||||
return const P04Page();
|
||||
case '5':
|
||||
return const P05Page();
|
||||
case '6':
|
||||
return const P06Page();
|
||||
case '7':
|
||||
return const P07Page();
|
||||
case '8':
|
||||
return const P08Page();
|
||||
case '9':
|
||||
return const P09Page();
|
||||
case '10':
|
||||
return const P10Page();
|
||||
case '11':
|
||||
return const P11Page();
|
||||
}
|
||||
return const EmptyPanel(msg: '暂未实现');
|
||||
},
|
||||
);
|
||||
@@ -1,37 +1,39 @@
|
||||
import 'package:components/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:iroute/v12/pages/empty/empty_panel.dart';
|
||||
import 'package:iroute/navigation/router/routers/anima.dart';
|
||||
import 'package:iroute/navigation/router/routers/dream.dart';
|
||||
import 'package:iroute/navigation/router/routers/render.dart';
|
||||
import 'package:iroute/navigation/router/routers/scroll.dart';
|
||||
import 'package:iroute/navigation/router/routers/touch.dart';
|
||||
|
||||
import '../../../navigation/app_navigation.dart';
|
||||
import '../../views/app_navigation.dart';
|
||||
import '../../../pages/empty/empty_panel.dart';
|
||||
import 'dashboard.dart';
|
||||
import 'draw.dart';
|
||||
import 'layout.dart';
|
||||
|
||||
|
||||
final RouteBase appRoute = ShellRoute(
|
||||
builder: (BuildContext context, GoRouterState state, Widget child) {
|
||||
return BookAppNavigation(content: child);
|
||||
return TolyBookNavigation(content: child);
|
||||
},
|
||||
routes: <RouteBase>[
|
||||
dashboardRouters,
|
||||
drawRouters,
|
||||
// GoRoute(
|
||||
// path: 'counter',
|
||||
// builder: (BuildContext context, GoRouterState state) {
|
||||
// return const CounterPage();
|
||||
// }),
|
||||
// sortRouters,
|
||||
// GoRoute(
|
||||
// path: 'user',
|
||||
// builder: (BuildContext context, GoRouterState state) {
|
||||
// return const UserPage();
|
||||
// },
|
||||
// ),
|
||||
// GoRoute(
|
||||
// path: 'settings',
|
||||
// builder: (BuildContext context, GoRouterState state) {
|
||||
// return const SettingPage();
|
||||
// },
|
||||
// ),
|
||||
touchRouters,
|
||||
dreamRouters,
|
||||
scrollRouters,
|
||||
renderRouters,
|
||||
layoutRouters,
|
||||
animaRouters,
|
||||
GoRoute(
|
||||
path: '/code',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
String? path = state.uri.queryParameters['path'];
|
||||
return CodeView(path: path??'',);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/404',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
|
||||
@@ -1,82 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:idraw/idraw.dart';
|
||||
import 'package:iroute/pages/dashboard/chat_room.dart';
|
||||
import 'package:iroute/pages/empty/empty_panel.dart';
|
||||
|
||||
final RouteBase drawRouters = GoRoute(
|
||||
path: '/draw',
|
||||
redirect: (_, state) {
|
||||
if (state.fullPath == '/draw') {
|
||||
return '/draw/chapter1';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: 'chapter1',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
path: '/draw/chapter:index',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
String? index = state.pathParameters['index'];
|
||||
switch(index){
|
||||
case '1':
|
||||
return const P01Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter2',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '2':
|
||||
return const P02Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter3',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '3':
|
||||
return const P03Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter4',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '4':
|
||||
return const P04Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter5',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '5':
|
||||
return const P05Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter6',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '6':
|
||||
return const P06Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter7',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '7':
|
||||
return const P07Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter8',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '8':
|
||||
return const P08Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter9',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '9':
|
||||
return const P09Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter10',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '10':
|
||||
return const P10Page();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chapter11',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
case '11':
|
||||
return const P11Page();
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
return const EmptyPanel(msg: '暂未实现');
|
||||
},
|
||||
);
|
||||
|
||||
36
lib/navigation/router/routers/dream.dart
Normal file
36
lib/navigation/router/routers/dream.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:idraw/idraw.dart';
|
||||
import 'package:iroute/pages/empty/empty_panel.dart';
|
||||
|
||||
final RouteBase dreamRouters = GoRoute(
|
||||
path: '/dream/chapter:index',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
String? index = state.pathParameters['index'];
|
||||
switch(index){
|
||||
case '1':
|
||||
return const P01Page();
|
||||
case '2':
|
||||
return const P01Page();
|
||||
case '3':
|
||||
return const P03Page();
|
||||
case '4':
|
||||
return const P04Page();
|
||||
case '5':
|
||||
return const P05Page();
|
||||
case '6':
|
||||
return const P06Page();
|
||||
case '7':
|
||||
return const P07Page();
|
||||
case '8':
|
||||
return const P08Page();
|
||||
case '9':
|
||||
return const P09Page();
|
||||
case '10':
|
||||
return const P10Page();
|
||||
case '11':
|
||||
return const P11Page();
|
||||
}
|
||||
return const EmptyPanel(msg: '暂未实现');
|
||||
},
|
||||
);
|
||||
36
lib/navigation/router/routers/layout.dart
Normal file
36
lib/navigation/router/routers/layout.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:idraw/idraw.dart';
|
||||
import 'package:iroute/pages/empty/empty_panel.dart';
|
||||
|
||||
final RouteBase layoutRouters = GoRoute(
|
||||
path: '/layout/chapter:index',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
String? index = state.pathParameters['index'];
|
||||
switch(index){
|
||||
case '1':
|
||||
return const P01Page();
|
||||
case '2':
|
||||
return const P01Page();
|
||||
case '3':
|
||||
return const P03Page();
|
||||
case '4':
|
||||
return const P04Page();
|
||||
case '5':
|
||||
return const P05Page();
|
||||
case '6':
|
||||
return const P06Page();
|
||||
case '7':
|
||||
return const P07Page();
|
||||
case '8':
|
||||
return const P08Page();
|
||||
case '9':
|
||||
return const P09Page();
|
||||
case '10':
|
||||
return const P10Page();
|
||||
case '11':
|
||||
return const P11Page();
|
||||
}
|
||||
return const EmptyPanel(msg: '暂未实现');
|
||||
},
|
||||
);
|
||||
36
lib/navigation/router/routers/render.dart
Normal file
36
lib/navigation/router/routers/render.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:idraw/idraw.dart';
|
||||
import 'package:iroute/pages/empty/empty_panel.dart';
|
||||
|
||||
final RouteBase renderRouters = GoRoute(
|
||||
path: '/render/chapter:index',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
String? index = state.pathParameters['index'];
|
||||
switch(index){
|
||||
case '1':
|
||||
return const P01Page();
|
||||
case '2':
|
||||
return const P01Page();
|
||||
case '3':
|
||||
return const P03Page();
|
||||
case '4':
|
||||
return const P04Page();
|
||||
case '5':
|
||||
return const P05Page();
|
||||
case '6':
|
||||
return const P06Page();
|
||||
case '7':
|
||||
return const P07Page();
|
||||
case '8':
|
||||
return const P08Page();
|
||||
case '9':
|
||||
return const P09Page();
|
||||
case '10':
|
||||
return const P10Page();
|
||||
case '11':
|
||||
return const P11Page();
|
||||
}
|
||||
return const EmptyPanel(msg: '暂未实现');
|
||||
},
|
||||
);
|
||||
36
lib/navigation/router/routers/scroll.dart
Normal file
36
lib/navigation/router/routers/scroll.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:idraw/idraw.dart';
|
||||
import 'package:iroute/pages/empty/empty_panel.dart';
|
||||
|
||||
final RouteBase scrollRouters = GoRoute(
|
||||
path: '/scroll/chapter:index',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
String? index = state.pathParameters['index'];
|
||||
switch(index){
|
||||
case '1':
|
||||
return const P01Page();
|
||||
case '2':
|
||||
return const P01Page();
|
||||
case '3':
|
||||
return const P03Page();
|
||||
case '4':
|
||||
return const P04Page();
|
||||
case '5':
|
||||
return const P05Page();
|
||||
case '6':
|
||||
return const P06Page();
|
||||
case '7':
|
||||
return const P07Page();
|
||||
case '8':
|
||||
return const P08Page();
|
||||
case '9':
|
||||
return const P09Page();
|
||||
case '10':
|
||||
return const P10Page();
|
||||
case '11':
|
||||
return const P11Page();
|
||||
}
|
||||
return const EmptyPanel(msg: '暂未实现');
|
||||
},
|
||||
);
|
||||
36
lib/navigation/router/routers/touch.dart
Normal file
36
lib/navigation/router/routers/touch.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:idraw/idraw.dart';
|
||||
import 'package:iroute/pages/empty/empty_panel.dart';
|
||||
|
||||
final RouteBase touchRouters = GoRoute(
|
||||
path: '/touch/chapter:index',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
String? index = state.pathParameters['index'];
|
||||
switch(index){
|
||||
case '1':
|
||||
return const P01Page();
|
||||
case '2':
|
||||
return const P01Page();
|
||||
case '3':
|
||||
return const P03Page();
|
||||
case '4':
|
||||
return const P04Page();
|
||||
case '5':
|
||||
return const P05Page();
|
||||
case '6':
|
||||
return const P06Page();
|
||||
case '7':
|
||||
return const P07Page();
|
||||
case '8':
|
||||
return const P08Page();
|
||||
case '9':
|
||||
return const P09Page();
|
||||
case '10':
|
||||
return const P10Page();
|
||||
case '11':
|
||||
return const P11Page();
|
||||
}
|
||||
return const EmptyPanel(msg: '暂未实现');
|
||||
},
|
||||
);
|
||||
53
lib/navigation/views/app_navigation.dart
Normal file
53
lib/navigation/views/app_navigation.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'package:components/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toly_menu/src/menu.dart';
|
||||
import 'package:toly_menu/toly_menu.dart';
|
||||
import '../router/menus/menu_scope/menu_scope.dart';
|
||||
import 'menu_record.dart';
|
||||
import 'top_logo.dart';
|
||||
import 'top_bar.dart';
|
||||
|
||||
class TolyBookNavigation extends StatelessWidget {
|
||||
final Widget content;
|
||||
const TolyBookNavigation({super.key, required this.content});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
DragToMoveWrap(
|
||||
child: Container(
|
||||
color: const Color(0xff001529),
|
||||
width: 210,
|
||||
child: Column(
|
||||
children: [
|
||||
TopLogo(),
|
||||
Expanded(child: MenuTreeView()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
ColoredBox(
|
||||
color: const Color(0xffF2F2F2),
|
||||
child: AppTopBar()),
|
||||
MenuRecord() ,
|
||||
Expanded(child: content)],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MenuTreeView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MenuStore store = MenuScope.of(context);
|
||||
return TolyMenu(state: store.state, onSelect: store.select);
|
||||
}
|
||||
}
|
||||
131
lib/navigation/views/menu_record.dart
Normal file
131
lib/navigation/views/menu_record.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/src/gestures/events.dart';
|
||||
import 'package:iroute/navigation/router/menus/menu_scope/menu_history.dart';
|
||||
import 'package:iroute/navigation/router/menus/menu_scope/menu_scope.dart';
|
||||
|
||||
class MenuRecord extends StatelessWidget {
|
||||
const MenuRecord({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MenuStore store = MenuScope.of(context);
|
||||
List<MenuHistory> history = MenuScope.of(context).history;
|
||||
const BorderSide side = BorderSide(color: Color(0xffE8E8E8), width: 1);
|
||||
Color themeColor = Theme.of(context).primaryColor;
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF2F2F2),
|
||||
border: Border(top: side, bottom: side)),
|
||||
height: 28,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: history
|
||||
.map((e) => RecordTab(
|
||||
canClose: history.length > 1,
|
||||
onCloseHistory: store.closeHistory,
|
||||
onTapHistory: store.selectHistory,
|
||||
active: store.state.activeMenu == e.menuPath,
|
||||
history: e,
|
||||
))
|
||||
.toList()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RecordTab extends StatelessWidget {
|
||||
final bool active;
|
||||
final bool canClose;
|
||||
final MenuHistory history;
|
||||
final ValueChanged<MenuHistory> onCloseHistory;
|
||||
final ValueChanged<String> onTapHistory;
|
||||
const RecordTab({
|
||||
super.key,
|
||||
this.active = false,
|
||||
required this.canClose,
|
||||
required this.history,
|
||||
required this.onCloseHistory,
|
||||
required this.onTapHistory,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: ()=>onTapHistory(history.menuPath),
|
||||
child: ColoredBox(
|
||||
color: active ? Colors.white : Colors.transparent,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
history.menuLabel,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
if(canClose)
|
||||
CloseHoverButton(
|
||||
onPressed: ()=>onCloseHistory(history)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CloseHoverButton extends StatefulWidget {
|
||||
final VoidCallback onPressed;
|
||||
const CloseHoverButton({super.key, required this.onPressed});
|
||||
|
||||
@override
|
||||
State<CloseHoverButton> createState() => _CloseHoverButtonState();
|
||||
}
|
||||
|
||||
class _CloseHoverButtonState extends State<CloseHoverButton> {
|
||||
bool _isHover = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.onPressed,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onExit: _onExit,
|
||||
onEnter: _onEnter,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
color: _isHover ? Color(0xffBFC5C8) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
child: Icon(
|
||||
CupertinoIcons.clear_thick,
|
||||
size: 10,
|
||||
color: _isHover ? Colors.white : Colors.grey,
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onExit(PointerExitEvent event) {
|
||||
setState(() {
|
||||
_isHover = false;
|
||||
});
|
||||
}
|
||||
|
||||
void _onEnter(PointerEnterEvent event) {
|
||||
setState(() {
|
||||
_isHover = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,162 @@
|
||||
import 'package:components/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../components/components.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:iroute/navigation/router/menus/menu_scope/menu_scope.dart';
|
||||
|
||||
class AppTopBar extends StatelessWidget {
|
||||
const AppTopBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
// String? lable = MenuScope.of(context).currentNode?.label;
|
||||
return DragToMoveWrap(
|
||||
child: const Row(
|
||||
children: [
|
||||
SizedBox(width: 20,),
|
||||
// Text(
|
||||
// '404 界面丢失',
|
||||
// style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
Spacer(),
|
||||
WindowButtons()
|
||||
],
|
||||
child: Stack(
|
||||
alignment: Alignment.centerRight,
|
||||
children: [Row(
|
||||
children: [
|
||||
SizedBox(width: 20,),
|
||||
RouteBackIndicator(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: RouterIndicator(),
|
||||
),
|
||||
// Text(
|
||||
// '$lable',
|
||||
// style: TextStyle(fontSize: 14),
|
||||
// ),
|
||||
Spacer(),
|
||||
],
|
||||
),WindowButtons()]
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
class RouteBackIndicator extends StatefulWidget {
|
||||
const RouteBackIndicator({super.key});
|
||||
|
||||
@override
|
||||
State<RouteBackIndicator> createState() => _RouteBackIndicatorState();
|
||||
}
|
||||
|
||||
class _RouteBackIndicatorState extends State<RouteBackIndicator> {
|
||||
|
||||
late GoRouterDelegate _delegate ;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_delegate = GoRouter.of(context).routerDelegate;
|
||||
_delegate.addListener(_onChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_delegate.removeListener(_onChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool hasPush = _delegate.currentConfiguration.matches
|
||||
.whereType<ImperativeRouteMatch>().isNotEmpty;
|
||||
if(hasPush){
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: context.pop,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
margin: EdgeInsets.only(right: 8),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xffE3E5E7),
|
||||
borderRadius: BorderRadius.circular(6)
|
||||
),
|
||||
child: Icon(Icons.arrow_back_ios_new,size: 14,)),
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
void _onChange() {
|
||||
setState(() {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
class RouterIndicator extends StatefulWidget {
|
||||
const RouterIndicator({super.key});
|
||||
|
||||
@override
|
||||
State<RouterIndicator> createState() => _RouterIndicatorState();
|
||||
}
|
||||
|
||||
class _RouterIndicatorState extends State<RouterIndicator> {
|
||||
late GoRouterDelegate _delegate;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_delegate = GoRouter.of(context).routerDelegate;
|
||||
_delegate.addListener(_onRouterChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_delegate.removeListener(_onRouterChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<RouteMatch> matches = _delegate.currentConfiguration.matches;
|
||||
if(matches.isEmpty) return const SizedBox();
|
||||
RouteMatch match = _delegate.currentConfiguration.matches.last;
|
||||
|
||||
print(
|
||||
"=========_RouterIndicatorState:build==${match.matchedLocation}========");
|
||||
|
||||
return TolyBreadcrumb(
|
||||
fontSize: 12,
|
||||
items: pathToBreadcrumbItems(context, match.matchedLocation),
|
||||
onTapItem: (item) {
|
||||
if (item.to != null) {
|
||||
GoRouter.of(context).go(item.to!);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onRouterChange() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
List<BreadcrumbItem> pathToBreadcrumbItems(
|
||||
BuildContext context, String path) {
|
||||
Uri uri = Uri.parse(path);
|
||||
List<BreadcrumbItem> result = [];
|
||||
String to = '';
|
||||
|
||||
String distPath = '';
|
||||
for (String segment in uri.pathSegments) {
|
||||
distPath += '/$segment';
|
||||
}
|
||||
|
||||
for (String segment in uri.pathSegments) {
|
||||
to += '/$segment';
|
||||
String? label;
|
||||
if(to=='/code'){
|
||||
label = '代码详情';
|
||||
}else{
|
||||
label = MenuScope.read(context).pathName(to);
|
||||
}
|
||||
|
||||
if (label!=null&&label.isNotEmpty) {
|
||||
result
|
||||
.add(BreadcrumbItem(to: to, label: label, active: to == distPath));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import '../view_books.dart';
|
||||
|
||||
class BookCell extends StatefulWidget {
|
||||
final BookInfo bookInfo;
|
||||
const BookCell({super.key, required this.bookInfo});
|
||||
final ValueChanged<BookInfo> onSelect;
|
||||
const BookCell({super.key, required this.bookInfo, required this.onSelect});
|
||||
|
||||
@override
|
||||
State<BookCell> createState() => _BookCellState();
|
||||
@@ -14,31 +15,34 @@ class BookCell extends StatefulWidget {
|
||||
class _BookCellState extends State<BookCell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: _onEnter,
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(child: ClipRRect(
|
||||
child: Image.asset(widget.bookInfo.cover),
|
||||
borderRadius:BorderRadius.circular(8)
|
||||
))),
|
||||
const SizedBox(height: 8),
|
||||
return GestureDetector(
|
||||
onTap: ()=> widget.onSelect(widget.bookInfo),
|
||||
child: MouseRegion(
|
||||
onEnter: _onEnter,
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Center(child: ClipRRect(
|
||||
child: Image.asset(widget.bookInfo.cover),
|
||||
borderRadius:BorderRadius.circular(8)
|
||||
))),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(
|
||||
'${widget.bookInfo.info}',
|
||||
style:
|
||||
TextStyle(fontSize: 12, color: Color(0xff515767)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(
|
||||
'${widget.bookInfo.info}',
|
||||
style:
|
||||
TextStyle(fontSize: 12, color: Color(0xff515767)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:components/toly_ui/decoration/title.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../components/toly_ui/decoration/title.dart';
|
||||
|
||||
class TitleGroup extends StatelessWidget {
|
||||
final String title;
|
||||
final Color? color;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:iroute/navigation/router/menus/menu_scope/menu_scope.dart';
|
||||
|
||||
import 'view_book/book_cell.dart';
|
||||
import 'view_book/title_group.dart';
|
||||
@@ -121,14 +123,14 @@ class ViewBooks extends StatelessWidget {
|
||||
lineColor: const Color(0xff6EAFF9),
|
||||
),
|
||||
),
|
||||
_buildSliverSliverGrid(kBooks,gridDelegate),
|
||||
_buildSliverSliverGrid(context,kBooks,gridDelegate),
|
||||
SliverToBoxAdapter(
|
||||
child: TitleGroup(
|
||||
title: 'Flutter 实战探索',
|
||||
lineColor: const Color(0xffFD983A),
|
||||
),
|
||||
),
|
||||
_buildSliverSliverGrid(projectBooks,gridDelegate),
|
||||
_buildSliverSliverGrid(context,projectBooks,gridDelegate),
|
||||
|
||||
|
||||
SliverToBoxAdapter(
|
||||
@@ -137,18 +139,22 @@ class ViewBooks extends StatelessWidget {
|
||||
lineColor: const Color(0xff7864E1),
|
||||
),
|
||||
),
|
||||
_buildSliverSliverGrid(freeBooks,gridDelegate),
|
||||
_buildSliverSliverGrid(context,freeBooks,gridDelegate),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSliverSliverGrid(
|
||||
Widget _buildSliverSliverGrid(BuildContext context,
|
||||
List<BookInfo> books, SliverGridDelegate gridDelegate) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
sliver: SliverGrid(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(_, i) => BookCell(bookInfo: books[i]),
|
||||
(_, i) => BookCell(bookInfo: books[i],onSelect: (b){
|
||||
if(b.path == 'draw'){
|
||||
context.go('/draw/chapter1');
|
||||
}
|
||||
},),
|
||||
childCount: books.length,
|
||||
),
|
||||
gridDelegate: gridDelegate),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:iroute/components/components.dart';
|
||||
|
||||
class EmptyPanel extends StatelessWidget {
|
||||
final String msg;
|
||||
@@ -21,12 +20,11 @@ class EmptyPanel extends StatelessWidget {
|
||||
style: TextStyle(fontSize: 24, color: Colors.grey),
|
||||
),
|
||||
ElevatedButton(onPressed: (){
|
||||
context.go('/');
|
||||
context.go('/dashboard/view');
|
||||
}, child: Text('返回首页'))
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export 'app/unit_app.dart';
|
||||
@@ -1,114 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../pages/color/color_page.dart';
|
||||
import '../../pages/empty/empty_page.dart';
|
||||
import '../../pages/settings/settings_page.dart';
|
||||
import '../../pages/counter/counter_page.dart';
|
||||
import '../../pages/user/user_page.dart';
|
||||
import '../../transition/fade_transition_page.dart';
|
||||
import '../../transition/no_transition_page.dart';
|
||||
|
||||
const List<String> kDestinationsPaths = [
|
||||
'/color',
|
||||
'/counter',
|
||||
'/user',
|
||||
'/settings',
|
||||
];
|
||||
|
||||
AppRouterDelegate router = AppRouterDelegate();
|
||||
|
||||
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
||||
String _path = '/color';
|
||||
|
||||
String get path => _path;
|
||||
|
||||
set path(String value) {
|
||||
if (_path == value) return;
|
||||
_path = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
onPopPage: _onPopPage,
|
||||
pages: _buildPageByPath(path),
|
||||
);
|
||||
}
|
||||
|
||||
List<Page> _buildPageByPath(String path) {
|
||||
Widget? child;
|
||||
if (path == kDestinationsPaths[0]) {
|
||||
child = const ColorPage();
|
||||
}
|
||||
if (path == kDestinationsPaths[1]) {
|
||||
child = const CounterPage();
|
||||
}
|
||||
if (path == kDestinationsPaths[2]) {
|
||||
child = const UserPage();
|
||||
}
|
||||
if (path == kDestinationsPaths[3]) {
|
||||
child = const SettingPage();
|
||||
}
|
||||
return [
|
||||
FadeTransitionPage(
|
||||
key: ValueKey(path),
|
||||
child: child ?? const EmptyPage(),
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> popRoute() async {
|
||||
print('=======popRoute=========');
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _onPopPage(Route route, result) {
|
||||
return route.didPop(result);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setNewRoutePath(configuration) async {}
|
||||
}
|
||||
|
||||
// class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier, PopNavigatorRouterDelegateMixin {
|
||||
//
|
||||
// List<String> _value = ['/'];
|
||||
//
|
||||
//
|
||||
// List<String> get value => _value;
|
||||
//
|
||||
// set value(List<String> value){
|
||||
// _value = value;
|
||||
// notifyListeners();
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Navigator(
|
||||
// onPopPage: _onPopPage,
|
||||
// pages: _value.map((e) => _pageMap[e]!).toList(),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// final Map<String, Page> _pageMap = const {
|
||||
// '/': MaterialPage(child: HomePage()),
|
||||
// 'a': MaterialPage(child: PageA()),
|
||||
// 'b': MaterialPage(child: PageB()),
|
||||
// 'c': MaterialPage(child: PageC()),
|
||||
// };
|
||||
//
|
||||
// bool _onPopPage(Route route, result) {
|
||||
// _value = List.of(_value)..removeLast();
|
||||
// notifyListeners();
|
||||
// return route.didPop(result);
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// GlobalKey<NavigatorState>? navigatorKey = GlobalKey<NavigatorState>();
|
||||
//
|
||||
// @override
|
||||
// Future<void> setNewRoutePath(String configuration) async{
|
||||
// }
|
||||
// }
|
||||
@@ -1,53 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../app_router_delegate.dart';
|
||||
|
||||
class AppNavigationRail extends StatefulWidget {
|
||||
const AppNavigationRail({super.key});
|
||||
|
||||
@override
|
||||
State<AppNavigationRail> createState() => _AppNavigationRailState();
|
||||
}
|
||||
|
||||
class _AppNavigationRailState extends State<AppNavigationRail> {
|
||||
final List<NavigationRailDestination> destinations = const [
|
||||
NavigationRailDestination(icon: Icon(Icons.color_lens_outlined), label: Text("颜色板")),
|
||||
NavigationRailDestination(icon: Icon(Icons.add_chart), label: Text("计数器")),
|
||||
NavigationRailDestination(icon: Icon(Icons.person), label: Text("我的")),
|
||||
NavigationRailDestination(icon: Icon(Icons.settings), label: Text("设置")),
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
router.addListener(_onRouterChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
router.removeListener(_onRouterChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
int _index = 0 ;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NavigationRail(
|
||||
labelType: NavigationRailLabelType.all,
|
||||
onDestinationSelected: _onDestinationSelected,
|
||||
destinations: destinations,
|
||||
selectedIndex: _index,
|
||||
);
|
||||
}
|
||||
|
||||
void _onDestinationSelected(int index) {
|
||||
router.path = kDestinationsPaths[index];
|
||||
}
|
||||
|
||||
void _onRouterChange() {
|
||||
_index = kDestinationsPaths.indexOf(router.path);
|
||||
setState(() {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'navigation/app_router_delegate.dart';
|
||||
import 'navigation/views/app_navigation_rail.dart';
|
||||
|
||||
class UnitApp extends StatelessWidget {
|
||||
const UnitApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
appBarTheme: const AppBarTheme(
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(color: Colors.black),
|
||||
titleTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
))),
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
const AppNavigationRail(),
|
||||
Expanded(
|
||||
child: Router(
|
||||
routerDelegate: router,
|
||||
backButtonDispatcher: RootBackButtonDispatcher(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/components/project/colors_panel.dart';
|
||||
|
||||
class ColorPage extends StatefulWidget {
|
||||
const ColorPage({super.key});
|
||||
|
||||
@override
|
||||
State<ColorPage> createState() => _ColorPageState();
|
||||
}
|
||||
|
||||
class _ColorPageState extends State<ColorPage> {
|
||||
final List<Color> _colors = [
|
||||
Colors.red, Colors.black, Colors.blue, Colors.green, Colors.orange,
|
||||
Colors.pink, Colors.purple, Colors.indigo, Colors.amber, Colors.cyan,
|
||||
Colors.redAccent, Colors.grey, Colors.blueAccent, Colors.greenAccent, Colors.orangeAccent,
|
||||
Colors.pinkAccent, Colors.purpleAccent, Colors.indigoAccent, Colors.amberAccent, Colors.cyanAccent,
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
// appBar: AppBar(title:const Text('颜色主页')),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _toAddPage,
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
body: ColorsPanel(
|
||||
colors: _colors,
|
||||
onSelect: _selectColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _selectColor(Color color){
|
||||
// String value = color.value.toRadixString(16);
|
||||
}
|
||||
|
||||
void _toAddPage() async {
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CounterPage extends StatefulWidget {
|
||||
const CounterPage({super.key});
|
||||
|
||||
@override
|
||||
State<CounterPage> createState() => _CounterPageState();
|
||||
}
|
||||
|
||||
class _CounterPageState extends State<CounterPage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EmptyPage extends StatelessWidget {
|
||||
const EmptyPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('界面走丢了'),
|
||||
),
|
||||
body: Scaffold(
|
||||
body: Center(
|
||||
child: Wrap(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
direction: Axis.vertical,
|
||||
children: [
|
||||
Icon(Icons.nearby_error,size: 64, color: Colors.grey),
|
||||
Text(
|
||||
'404 界面丢失',
|
||||
style: TextStyle(fontSize: 24, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user