add breadcrumbs and allow clicking on disks
This commit is contained in:
		
							
								
								
									
										127
									
								
								app/lib/breadcrumb_page.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								app/lib/breadcrumb_page.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<String> 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<String> _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<BreadCrumbPage> createState() => _BreadCrumbPageState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _BreadCrumbPageState extends State<BreadCrumbPage> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return ChangeNotifierProvider<BreadCrumbController>(
 | 
			
		||||
      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<BreadCrumbController>(
 | 
			
		||||
              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<BreadCrumbController>(
 | 
			
		||||
            builder: (_, value, __) => value.getCurrentPage(),
 | 
			
		||||
          )
 | 
			
		||||
          // currentPage
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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<DiskPage> {
 | 
			
		||||
  Future<List<Disk>> 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<List<Disk>> 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<BreadCrumbController>(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",
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import 'package:raid_manager/raid_page.dart';
 | 
			
		||||
import 'package:sidebarx/sidebarx.dart';
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
  print("starting");
 | 
			
		||||
  runApp(SidebarXExampleApp());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,27 +24,44 @@ class _RaidPageState extends State<RaidPage> {
 | 
			
		||||
      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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user