This commit is contained in:
toly
2023-12-16 12:40:32 +08:00
parent ab2778a22b
commit 01fdf966c5
593 changed files with 8995 additions and 27753 deletions

View File

@@ -1,3 +0,0 @@
export 'toly_ui/toly_ui.dart';
export 'windows/window_buttons.dart';
export 'windows/drag_to_move_area.dart';

View File

@@ -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(),
),
);
}
}

View File

@@ -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;
});
}
}

View File

@@ -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();
}
}

View File

@@ -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,
});
}

View File

@@ -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,});
}

View File

@@ -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),
)
],
),
),
);
}
}

View File

@@ -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),)),
),
),
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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

View File

@@ -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';

View File

@@ -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,
);
}
}

View File

@@ -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();
},
),
),
],
),
);
}
}

View File

@@ -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();

View File

@@ -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(() {});
}
}

View File

@@ -1,6 +1,9 @@
import '../../../app/res/fx_icon.dart';
Map<String,dynamic> animaMenus = {
'path' : '/anima',
'label' : '动画-流光幻影',
'icon': FxIcon.icon_anima,
'children': [
{
'path' : '/chapter1',

View 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': '第三交流区',
},
]
},
],
};

View File

@@ -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': [
{

View File

@@ -1,6 +1,9 @@
import '../../../app/res/fx_icon.dart';
Map<String,dynamic> dreamMenus = {
'path' : '/dream',
'label' : '基础-梦始之地',
'icon': FxIcon.icon_dream,
'children': [
{
'path' : '/chapter1',

View File

@@ -1,6 +1,10 @@
import '../../../app/res/fx_icon.dart';
Map<String,dynamic> layoutMenus = {
'path' : '/layout',
'label' : '布局-薪火相传',
'icon': FxIcon.icon_layout,
'children': [
{
'path' : '/chapter1',

View File

@@ -0,0 +1,9 @@
class MenuHistory {
final String menuLabel;
final String menuPath;
MenuHistory({
required this.menuLabel,
required this.menuPath,
});
}

View 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);
}
}
}

View File

@@ -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,

View File

@@ -1,6 +1,10 @@
import '../../../app/res/fx_icon.dart';
Map<String,dynamic> renderMenus = {
'path' : '/render',
'label' : '渲染-聚沙成塔',
'icon': FxIcon.icon_sun,
'children': [
{
'path' : '/chapter1',

View File

@@ -1,6 +1,10 @@
import '../../../app/res/fx_icon.dart';
Map<String,dynamic> scrollMenus = {
'path' : '/scroll',
'label' : '滑动-珠联璧合',
'icon': FxIcon.icon_scroll,
'children': [
{
'path' : '/chapter1',

View File

@@ -1,6 +1,9 @@
import '../../../app/res/fx_icon.dart';
Map<String,dynamic> touchMenus = {
'path' : '/touch',
'label' : '手势-执掌天下',
'icon': FxIcon.fill_shoushi,
'children': [
{
'path' : '/chapter1',

View 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: '暂未实现');
},
);

View File

@@ -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) {

View File

@@ -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: '暂未实现');
},
);

View 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: '暂未实现');
},
);

View 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: '暂未实现');
},
);

View 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: '暂未实现');
},
);

View 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: '暂未实现');
},
);

View 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: '暂未实现');
},
);

View 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);
}
}

View 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;
});
}
}

View File

@@ -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;
}
}

View File

@@ -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)),
),
),
),
],
],
),
),
),
),

View File

@@ -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;

View File

@@ -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),

View File

@@ -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('返回首页'))
],
),
),
);
}
}
}

View File

@@ -1 +0,0 @@
export 'app/unit_app.dart';

View File

@@ -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{
// }
// }

View File

@@ -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(() {
});
}
}

View File

@@ -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(),
),
),
],
),
));
}
}

View File

@@ -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 {
}
}

View File

@@ -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),
),
);
}
}

View File

@@ -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