From ceeae0523e7ce92df65e7e3a3c3e97419bccd8aa Mon Sep 17 00:00:00 2001 From: lukas-heiligenbrunner Date: Fri, 9 Dec 2022 01:45:00 +0100 Subject: [PATCH] add breadcrumbs and allow clicking on disks --- app/lib/breadcrumb_page.dart | 127 +++++++++++++++++++++++++++++++++++ app/lib/disk_page.dart | 85 +++++++++++++++-------- app/lib/main.dart | 1 + app/lib/raid_page.dart | 57 ++++++++++------ app/pubspec.lock | 28 ++++++++ app/pubspec.yaml | 2 + 6 files changed, 251 insertions(+), 49 deletions(-) create mode 100644 app/lib/breadcrumb_page.dart diff --git a/app/lib/breadcrumb_page.dart b/app/lib/breadcrumb_page.dart new file mode 100644 index 0000000..8fc184a --- /dev/null +++ b/app/lib/breadcrumb_page.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; +import 'package:provider/provider.dart'; + +class _BreadCrumbNode { + String name; + Widget widget; + _BreadCrumbNode? next; + + _BreadCrumbNode(this.name, this.widget); +} + +class BreadCrumbController extends ChangeNotifier { + final Widget defaultRoute; + final String rootName; + + late _BreadCrumbNode _rootPage = _BreadCrumbNode(rootName, defaultRoute); + late Widget _currentpage = _rootPage.widget; + + void pushPage(Widget widget, String name) { + _last(_rootPage).next = _BreadCrumbNode(name, widget); + _currentpage = widget; + notifyListeners(); + } + + void switchPage(String name) { + final pageNode = _findPage(name, _rootPage); + if (pageNode != null) { + _currentpage = pageNode.widget; + // hope for garbage collector to free the floating items (; + pageNode.next = null; + } + notifyListeners(); + } + + List getNames() { + return _genNameList(_rootPage); + } + + Widget getCurrentPage() { + return _currentpage; + } + + _BreadCrumbNode _last(_BreadCrumbNode node) { + if (node.next != null) { + return _last(node.next!); + } else { + return node; + } + } + + _BreadCrumbNode? _findPage(String name, _BreadCrumbNode node) { + if (node.name == name) { + return node; + } + + if (node.next != null) { + return _findPage(name, node.next!); + } else { + return null; + } + } + + List _genNameList(_BreadCrumbNode node) { + if (node.next != null) { + return _genNameList(node.next!)..insert(0, node.name); + } else { + return [node.name]; + } + } + + BreadCrumbController(this.defaultRoute, this.rootName); +} + +class BreadCrumbPage extends StatefulWidget { + const BreadCrumbPage( + {Key? key, required this.mainPage, required this.rootName}) + : super(key: key); + + final Widget mainPage; + final String rootName; + + @override + State createState() => _BreadCrumbPageState(); +} + +class _BreadCrumbPageState extends State { + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (BuildContext context) => + BreadCrumbController(widget.mainPage, widget.rootName), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 12, top: 8, bottom: 8), + child: Consumer( + builder: (_, controller, __) => BreadCrumb( + items: controller + .getNames() + .map((e) => BreadCrumbItem( + padding: const EdgeInsets.all(2), + content: Text(e, + style: Theme.of(context).textTheme.labelMedium), + onTap: () { + controller.switchPage(e); + }, + )) + .toList(growable: false), + divider: Icon( + Icons.chevron_right, + color: Theme.of(context).textTheme.labelMedium?.color, + ), + ), + ), + ), + Divider(color: Colors.white.withOpacity(0.3), height: 1), + Consumer( + builder: (_, value, __) => value.getCurrentPage(), + ) + // currentPage + ], + ), + ); + } +} diff --git a/app/lib/disk_page.dart b/app/lib/disk_page.dart index e069fa9..ff9eb5a 100644 --- a/app/lib/disk_page.dart +++ b/app/lib/disk_page.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:raid_manager/api/request.dart'; +import 'package:raid_manager/breadcrumb_page.dart'; import 'package:raid_manager/types/disk.dart'; import 'package:raid_manager/utils/file_formatter.dart'; @@ -12,42 +14,67 @@ class DiskPage extends StatefulWidget { class _DiskPageState extends State { Future> fetchDisks() async { - return (await getJson('/api/disks') as List) + return ((await getJson('/api/disks')) as List) .map((e) => Disk.fromJson(e)) .toList(growable: false); } - late final myFetch = fetchDisks(); + @override + void initState() { + super.initState(); + myFetch = fetchDisks(); + } + + late Future> myFetch; + + Widget _buildMainPage() { + return Expanded( + child: FutureBuilder( + future: myFetch, + builder: (context, snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return const CircularProgressIndicator(); + } + + if (snapshot.hasError) { + print(snapshot.error); + return const Text("errored"); + } else if (snapshot.hasData) { + final data = snapshot.data!; + return ListView.builder( + itemCount: data.length, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (context, idx) { + return ListTile( + title: Text( + data[idx].name, + style: Theme.of(context).textTheme.headlineMedium, + ), + subtitle: Text( + data[idx].size.readableFileSize(), + style: Theme.of(context).textTheme.labelMedium, + ), + onTap: () { + Provider.of(context, listen: false) + .pushPage( + const Text("Mysupercool page"), data[idx].name); + }, + ); + }, + ); + } else { + return const Text("loading..."); + } + }, + ), + ); + } @override Widget build(BuildContext context) { - return FutureBuilder( - future: myFetch, - builder: (context, snapshot) { - if (snapshot.hasData) { - final data = snapshot.data!; - return ListView.builder( - itemCount: data.length, - itemBuilder: (context, idx) { - return ListTile( - title: Text( - data[idx].name, - style: Theme.of(context).textTheme.headlineMedium, - ), - subtitle: Text( - data[idx].size.readableFileSize(), - style: Theme.of(context).textTheme.labelMedium, - ), - onTap: () {}, - ); - }, - ); - } else if (snapshot.hasError) { - return const Text("errored"); - } else { - return const Text("loading..."); - } - }, + return BreadCrumbPage( + mainPage: _buildMainPage(), + rootName: "Disks", ); } } diff --git a/app/lib/main.dart b/app/lib/main.dart index a715020..b39b227 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -4,6 +4,7 @@ import 'package:raid_manager/raid_page.dart'; import 'package:sidebarx/sidebarx.dart'; void main() { + print("starting"); runApp(SidebarXExampleApp()); } diff --git a/app/lib/raid_page.dart b/app/lib/raid_page.dart index f821bd8..30f4751 100644 --- a/app/lib/raid_page.dart +++ b/app/lib/raid_page.dart @@ -24,27 +24,44 @@ class _RaidPageState extends State { builder: (context, snapshot) { if (snapshot.hasData) { final data = snapshot.data!; - return ListView.builder( - itemCount: data.raids.length, - itemBuilder: (context, idx) { - return ListTile( - title: Text( - data.raids[idx].name, - style: Theme.of(context).textTheme.headlineMedium, + + if (data.raids.isEmpty) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.warning_amber_rounded, + size: 42, color: Colors.white.withOpacity(.6)), + const SizedBox( + height: 7, ), - subtitle: Text( - "${data.raids[idx].level} - ${data.raids[idx].faulty ? "errored" : "active sync"}", - style: Theme.of(context).textTheme.labelMedium, - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => RaidInfoPage(raid: data.raids[idx]))); - }, - ); - }, - ); + Text("No Raid available", + style: Theme.of(context).textTheme.headlineMedium) + ], + ); + } else { + return ListView.builder( + itemCount: data.raids.length, + itemBuilder: (context, idx) { + return ListTile( + title: Text( + data.raids[idx].name, + style: Theme.of(context).textTheme.headlineMedium, + ), + subtitle: Text( + "${data.raids[idx].level} - ${data.raids[idx].faulty ? "errored" : "active sync"}", + style: Theme.of(context).textTheme.labelMedium, + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + RaidInfoPage(raid: data.raids[idx]))); + }, + ); + }, + ); + } } else if (snapshot.hasError) { return const Text("errored"); } else { diff --git a/app/pubspec.lock b/app/pubspec.lock index 1cc09fd..1d05cb7 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -181,6 +181,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_breadcrumb: + dependency: "direct dev" + description: + name: flutter_breadcrumb + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" flutter_lints: dependency: "direct dev" description: @@ -305,6 +312,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -319,6 +333,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.2" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" pool: dependency: transitive description: @@ -326,6 +347,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.1" + provider: + dependency: "direct dev" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.4" pub_semver: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index c95e168..a3e70f8 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -52,6 +52,8 @@ dev_dependencies: flutter_lints: ^2.0.0 build_runner: json_serializable: + flutter_breadcrumb: ^1.0.1 + provider: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec