Add base skeleton
This commit is contained in:
45
moneymgr_mobile/lib/widgets/app_button.dart
Normal file
45
moneymgr_mobile/lib/widgets/app_button.dart
Normal file
@ -0,0 +1,45 @@
|
||||
import 'package:flextras/flextras.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gutter/flutter_gutter.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:moneymgr_mobile/utils/extensions.dart';
|
||||
import 'package:moneymgr_mobile/utils/hooks.dart';
|
||||
|
||||
|
||||
/// A button that shows a circular progress indicator when the [onPressed] callback
|
||||
/// is pending.
|
||||
class AppButton extends HookWidget {
|
||||
const AppButton({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.label,
|
||||
});
|
||||
|
||||
final AsyncCallback? onPressed;
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final (:pending, :snapshot, hasError: _) = useAsyncTask();
|
||||
|
||||
return FilledButton(
|
||||
onPressed: onPressed == null ? null : () => pending.value = onPressed!(),
|
||||
child: SeparatedRow(
|
||||
separatorBuilder: () => const GutterSmall(),
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (snapshot.connectionState == ConnectionState.waiting)
|
||||
SizedBox.square(
|
||||
dimension: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: context.colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
Text(label),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
100
moneymgr_mobile/lib/widgets/scaffold_with_navigation.dart
Normal file
100
moneymgr_mobile/lib/widgets/scaffold_with_navigation.dart
Normal file
@ -0,0 +1,100 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// A scaffold that shows navigation bar/rail when the current path is a navigation
|
||||
/// item.
|
||||
///
|
||||
/// When in a navigation item, a [NavigationBar] will be shown if the width of the
|
||||
/// screen is less than 600dp. Otherwise, a [NavigationRail] will be shown.
|
||||
class ScaffoldWithNavigation extends StatelessWidget {
|
||||
const ScaffoldWithNavigation({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.selectedIndex,
|
||||
required this.navigationItems,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final int selectedIndex;
|
||||
final List<NavigationItem> navigationItems;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
void onDestinationSelected(int index) =>
|
||||
context.go(navigationItems[index].path);
|
||||
|
||||
// Use navigation rail instead of navigation bar when the screen width is
|
||||
// larger than 600dp.
|
||||
if (MediaQuery.sizeOf(context).width > 600) {
|
||||
return Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
NavigationRail(
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: onDestinationSelected,
|
||||
destinations: [
|
||||
for (final item in navigationItems)
|
||||
NavigationRailDestination(
|
||||
icon: Icon(item.icon),
|
||||
selectedIcon: item.selectedIcon != null
|
||||
? Icon(item.selectedIcon)
|
||||
: null,
|
||||
label: Text(item.label),
|
||||
)
|
||||
],
|
||||
extended: true,
|
||||
),
|
||||
Expanded(child: child),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: child,
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: onDestinationSelected,
|
||||
destinations: [
|
||||
for (final item in navigationItems)
|
||||
NavigationDestination(
|
||||
icon: Icon(item.icon),
|
||||
selectedIcon:
|
||||
item.selectedIcon != null ? Icon(item.selectedIcon) : null,
|
||||
label: item.label,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An item that represents a navigation destination in a navigation bar/rail.
|
||||
class NavigationItem {
|
||||
/// Path in the router.
|
||||
final String path;
|
||||
|
||||
/// Widget to show when navigating to this [path].
|
||||
final WidgetBuilder body;
|
||||
|
||||
/// Icon in the navigation bar.
|
||||
final IconData icon;
|
||||
|
||||
/// Icon in the navigation bar when selected.
|
||||
final IconData? selectedIcon;
|
||||
|
||||
/// Label in the navigation bar.
|
||||
final String label;
|
||||
|
||||
/// The subroutes of the route from this [path].
|
||||
final List<RouteBase> routes;
|
||||
|
||||
NavigationItem({
|
||||
required this.path,
|
||||
required this.body,
|
||||
required this.icon,
|
||||
this.selectedIcon,
|
||||
required this.label,
|
||||
this.routes = const [],
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user