diff --git a/notes/flutter_web/lib/routes.g.dart b/notes/flutter_web/lib/routes.g.dart index 9b8a25ae..e310cf6b 100644 --- a/notes/flutter_web/lib/routes.g.dart +++ b/notes/flutter_web/lib/routes.g.dart @@ -11,7 +11,7 @@ import 'package:you_flutter/router.dart'; import 'package:flutter_web/routes/page.dart'; import 'package:you_note_dart/note.dart'; import 'package:flutter_web/routes/notes/page.dart' as _notes_page; -import 'package:flutter_web/routes/notes/layout.dart' as _notes_layout; +import 'package:flutter_web/routes/notes/layout.dart'; import 'package:flutter_web/routes/notes/research/parameterized/page.dart' as _parameterized_page; import 'package:flutter_web/routes/notes/research/remote_view/page.dart' as _remote_view_page; import 'package:flutter_web/routes/notes/env_info/page.dart' as _env_info_page; @@ -67,7 +67,7 @@ import 'package:flutter_web/routes/notes/Improve_app/event&listener&lifeycle/pag mixin RoutesMixin { final To root = To('routes', builder: build, children: [ - ToNote('notes', builder: _notes_page.build, layout: _notes_layout.layout, children: [ + ToNote('notes', builder: _notes_page.build, layout: layout, children: [ ToNote('research', children: [ ToNote('bash_note'), ToNote('parameterized', builder: _parameterized_page.build), diff --git a/notes/flutter_web/lib/routes/layout.dart b/notes/flutter_web/lib/routes/layout.dart deleted file mode 100644 index 423e3028..00000000 --- a/notes/flutter_web/lib/routes/layout.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:you_flutter/router.dart'; - -/// ref: [PageLayoutBuilder] -Widget layout(BuildContext context, PageBuilder builder) { - // ignore: unnecessary_type_check - return RootLayout( - builder: builder, - ); -} - -@immutable -final class RootLayout extends StatelessWidget { - final PageBuilder builder; - - const RootLayout({super.key, required this.builder}); - - @override - Widget build(BuildContext context) { - var child = builder(context); - - NavigationRailDestination rail({required String title, required IconData icon}) { - return NavigationRailDestination( - icon: Tooltip(message: title, child: Icon(icon)), - label: Text(title), - ); - } - - return Scaffold( - primary: true, - // content... - appBar: AppBar(toolbarHeight: 38, title: const Text("widget.title"), actions: [ - IconButton(iconSize: 24, icon: const Icon(Icons.color_lens_outlined), onPressed: () {}), - IconButton(iconSize: 24, icon: const Icon(Icons.settings), onPressed: () {}), - if (kDebugMode) const Text("debug模式"), - ]), - floatingActionButton: FloatingActionButton(onPressed: () {}, tooltip: 'Increment', child: const Icon(Icons.add)), - body: SafeArea( - child: Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - NavigationRail(onDestinationSelected: (index) {}, minWidth: 24, minExtendedWidth: 24, selectedIndex: null, groupAlignment: -1, labelType: NavigationRailLabelType.none, destinations: [ - rail(title: "文件夹", icon: Icons.folder_outlined), - ]), - Drawer( - width: 200, - child: ListView(scrollDirection: Axis.vertical, children: [ - const Divider(), - ListTile(title: const Text('根页面'), subtitle: const Text("xxx"), onTap: () {}), - const Divider(), - ])), - Expanded(child: child), - ]), - ), - ); - } -} diff --git a/packages/you_flutter/lib/src/layouts/page_layout_default.dart b/packages/you_flutter/lib/src/layouts/page_layout_default.dart new file mode 100644 index 00000000..e1176df5 --- /dev/null +++ b/packages/you_flutter/lib/src/layouts/page_layout_default.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:you_flutter/src/router.dart'; + +class PageLayoutDefault extends StatelessWidget { + final PageBuilder builder; + final ToUri uri; + + PageLayoutDefault({required this.builder, required this.uri}); + + @override + Widget build(BuildContext context) { + var child = builder(context); + + NavigationRailDestination rail({required String title, required IconData icon}) { + return NavigationRailDestination( + icon: Tooltip(message: title, child: Icon(icon)), + label: Text(title), + ); + } + + return Scaffold( + primary: true, + // content... + appBar: AppBar(toolbarHeight: 38, title: Text("location: $uri"), actions: [ + IconButton(iconSize: 24, icon: const Icon(Icons.color_lens_outlined), onPressed: () {}), + IconButton(iconSize: 24, icon: const Icon(Icons.settings), onPressed: () {}), + ]), + floatingActionButton: FloatingActionButton(onPressed: () {}, tooltip: 'Increment', child: const Icon(Icons.add)), + body: SafeArea( + child: SelectionArea( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + NavigationRail(onDestinationSelected: (index) {}, minWidth: 24, minExtendedWidth: 24, selectedIndex: null, groupAlignment: -1, labelType: NavigationRailLabelType.none, destinations: [ + rail(title: "文件夹", icon: Icons.folder_outlined), + rail(title: "文件夹2", icon: Icons.folder_outlined), + ]), + Drawer( + width: 200, + child: _RouteTree(), + ), + Expanded(child: child), + ], + ), + ), + ), + ); + } +} + +class _RouteTree extends StatelessWidget { + const _RouteTree(); + + @override + Widget build(BuildContext context) { + final router = YouRouter.of(context); + + var validRoutes = router.root.toList(); + var routeWidgets = validRoutes.map((node) { + String title = "▼ ${node.part}"; + title = title.padLeft((node.level * 3) + title.length); + + var click = () { + router.to(node.toUri()); + }; + return Align( + alignment: Alignment.centerLeft, + child: TextButton(onPressed: click, child: Text(title)), + ); + }); + return ConstrainedBox( + constraints: BoxConstraints.tightFor(width: 350), + child: ListView( + children: [ + ...routeWidgets, + ], + )); + } +} diff --git a/packages/you_flutter/lib/src/router.dart b/packages/you_flutter/lib/src/router.dart index 55838012..a939887d 100644 --- a/packages/you_flutter/lib/src/router.dart +++ b/packages/you_flutter/lib/src/router.dart @@ -4,6 +4,7 @@ import 'dart:collection'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path/path.dart' as path_; +import 'package:you_flutter/src/layouts/page_layout_default.dart'; import 'package:you_flutter/src/log.dart'; /* @@ -41,6 +42,7 @@ ref: */ typedef PageBuilder = Widget Function(BuildContext context); +typedef PageLayoutBuilder = Widget Function(BuildContext context, PageBuilder bulider); typedef LazyPageBuilder = Future Function(); class NotFoundError extends ArgumentError { @@ -77,7 +79,6 @@ class RouteContext with RouterMixin { final YouRouter router; final ToUri uri; } - /// TODO P1 应针对2种flutter 支持的route模式进行适配: /// path base: https://example.com/product/1 /// fragment base: https://example.com/base-harf/#/product/1 @@ -87,7 +88,7 @@ class YouRouter with RouterMixin { required this.root, /// [PlatformRouteInformationProvider.initialRouteInformation] - required this.initial, + required Uri initial, required GlobalKey navigatorKey, }) : _navigatorKey = navigatorKey, assert(root.templatePath == "/") { @@ -100,7 +101,6 @@ class YouRouter with RouterMixin { } final To root; - final Uri initial; final GlobalKey _navigatorKey; late final RouterConfig _config; late final RouterDelegate _routerDelegate; @@ -113,6 +113,7 @@ class YouRouter with RouterMixin { RouterConfig config() => _config; + @visibleForTesting @override YouRouter get router => this; } @@ -154,14 +155,18 @@ base class To { late To _parent = this; final List children; + final PageBuilder? _builder; + final PageLayoutBuilder? _layout; // TODO P1 root Node的part是routes,有问题! To( this.part, { PageBuilder? builder, + PageLayoutBuilder? layout, this.children = const [], }) : _builder = builder, + _layout = layout, assert(part == "/" || !part.contains("/"), "part:'$part' should be '/' or legal directory name") { var parsed = _parse(part); _name = parsed.$1; @@ -207,8 +212,6 @@ base class To { bool get isLeaf => children.isEmpty; - bool get isValid => _builder != null; - // 对于page目录树: // - / -> uriTemplate: / // - users -> uriTemplate: /users @@ -217,6 +220,15 @@ base class To { List get ancestors => isRoot ? [] : [_parent, ..._parent.ancestors]; + /// return Strictly equal ancestors of type + Iterable findAncestorsOfSameType() sync* { + for (var a in ancestors) { + if (a.runtimeType == this.runtimeType) { + yield a as T; + } + } + } + To get root => isRoot ? this : _parent.root; int get level => isRoot ? 0 : _parent.level + 1; @@ -359,6 +371,20 @@ ${" " * level}'''; // TODO 临时实现,需要增加模版参数 return Uri.parse(templatePath); } + + @visibleForOverriding + Widget build(BuildContext context, ToUri uri) { + if (_builder == null) { + // FIXME NotFoundError如何处理 + throw NotFoundError(invalidValue: uri); + } + final List chain=[this,...findAncestorsOfSameType()]; + + for (var i in chain) { + if (i._layout != null) return i._layout(context, _builder); + } + return PageLayoutDefault(uri: uri, builder: _builder); + } } // TODO TOUri 设计的还不完善, @@ -543,7 +569,7 @@ class _RouterDelegate extends RouterDelegate with ChangeNotifier, PopNavi @override Future setNewRoutePath(ToUri configuration) { // TODO router暂时这样实现,还未确定Layout和route的配合细节 - stack.clear(); + // stack.clear(); stack.add(configuration); notifyListeners(); return SynchronousFuture(null); @@ -563,15 +589,6 @@ class _RouterDelegate extends RouterDelegate with ChangeNotifier, PopNavi @override Widget build(BuildContext context) { - Page buildPage(ToUri uri) { - if (uri.to._builder == null) { - // FIXME NotFoundError如何处理 - throw NotFoundError(invalidValue: uri); - } - // 在本router api稳定下来之前,不暴露flutter Page 相关api - return MaterialPage(key: ValueKey(uri), child: uri.to._builder!(context)); - } - return _RouteScope( uri: stack.first, router: router, @@ -589,7 +606,11 @@ class _RouterDelegate extends RouterDelegate with ChangeNotifier, PopNavi notifyListeners(); return true; }, - pages: List.from(stack.map((e) => buildPage(e))), + pages: List.from( + stack.map( + (uri) => MaterialPage(key: ValueKey(uri), child: uri.to.build(context, uri)), + ), + ), ); }, ); diff --git a/packages/you_note_dart/lib/src/layouts/note_layout_style_1.dart b/packages/you_note_dart/lib/src/layouts/note_layout_style_1.dart index 5c4c0c9b..528c49bc 100644 --- a/packages/you_note_dart/lib/src/layouts/note_layout_style_1.dart +++ b/packages/you_note_dart/lib/src/layouts/note_layout_style_1.dart @@ -50,7 +50,7 @@ class _NoteTreeView extends StatelessWidget { Widget build(BuildContext context) { final router = YouRouter.of(context); - var validRoutes = router.root.toList().where((e) => !e.isLeaf || (e.isValid)); + var validRoutes = router.root.toList().where((e) => !e.isLeaf ); var routeWidgets = validRoutes.map((node) { String title = "▼ ${node.part}"; title = title.padLeft((node.level * 3) + title.length); @@ -60,7 +60,7 @@ class _NoteTreeView extends StatelessWidget { }; return Align( alignment: Alignment.centerLeft, - child: TextButton(onPressed: node.isValid ? click : null, child: Text(title)), + child: TextButton(onPressed: click , child: Text(title)), ); }); return ConstrainedBox( diff --git a/packages/you_note_dart/lib/src/note.dart b/packages/you_note_dart/lib/src/note.dart index 237630e0..d5d25380 100644 --- a/packages/you_note_dart/lib/src/note.dart +++ b/packages/you_note_dart/lib/src/note.dart @@ -12,7 +12,6 @@ import 'package:http/http.dart' as http; import 'package:you_note_dart/src/layouts/note_layout_default.dart'; typedef NoteBuilder = void Function(BuildContext context, Cell print); -// TODO REMOVE typedef LazyNoteBuilder = Future Function(BuildContext context, Cell print); typedef NoteLayoutBuilder = Widget Function(BuildContext context, NoteBuilder builder); base class ToNote extends To { @@ -22,43 +21,18 @@ base class ToNote extends To { ToNote(super.part, {NoteBuilder? builder, NoteLayoutBuilder? layout, List children = const []}) : _builder = builder, _layout = layout, - super( - // builder: builder == null - // ? null - // : (context, uri) { - // Cell rootCell = Cell.empty(); - // builder(context, rootCell); - // // To? find = findLayoutNode(); - // // if (find == null) { - // // return NoteLayoutDefault(uri: uri, rootCell: rootCell); - // // } - // return Text(""); - // }, - // layout: layout == null - // ? null - // : (context, uri, pageBuilder) { - // var child = pageBuilder(context, uri); - // return layout(context, uri, child); - // }, - children: children); - - Widget? build(BuildContext context, ToUri uri) { + super(children: children); + + Widget build(BuildContext context, ToUri uri) { if (_builder == null) { - return null; + // TODO not found + return Text("not found $uri"); } - NoteLayoutBuilder? foundLayout = _findLayout(); - if (foundLayout == null) { - return NoteLayoutDefault(uri: uri, builder: _builder); + List chain = [this, ...findAncestorsOfSameType()]; + for (var i in chain) { + if (i._layout != null) return i._layout(context, _builder); } - return foundLayout(context, _builder); - } - - NoteLayoutBuilder? _findLayout() { - if (_layout != null) return _layout; - if (isRoot) return null; - To? p = parent; - if (p is! ToNote) return null; - return p._findLayout(); + return NoteLayoutDefault(uri: uri, builder: _builder); } } @@ -254,8 +228,7 @@ class NoteSystem { } base class Cell { - Cell( - Function(Cell print) callback, { + Cell(Function(Cell print) callback, { this.title, }) { callback(this);