|
import 'package:flutter/material.dart'; |
|
|
|
class AppScaffold extends StatefulWidget { |
|
final List<WidgetBuilder> pages; |
|
final List<BottomNavigationBarItem> navItems; |
|
|
|
const AppScaffold({ |
|
Key key, |
|
@required this.pages, |
|
@required this.navItems, |
|
}) : assert(pages.length == navItems.length, |
|
'must have an even number of pages and navItems'), |
|
super(key: key); |
|
|
|
@override |
|
_AppScaffoldState createState() => _AppScaffoldState(); |
|
|
|
static _AppScaffoldState of( |
|
BuildContext context, { |
|
bool isNullOk = false, |
|
}) { |
|
assert(isNullOk != null); |
|
assert(context != null); |
|
final _AppScaffoldState result = context.ancestorStateOfType( |
|
const TypeMatcher<_AppScaffoldState>(), |
|
) as _AppScaffoldState; |
|
if (isNullOk || result != null) { |
|
return result; |
|
} |
|
throw FlutterError( |
|
'PageContainer.of() called with a context that does not contain a PageContainer.\n', |
|
); |
|
} |
|
|
|
static NavigatorState tabNavigatorOf( |
|
BuildContext context, { |
|
@required int tabIndex, |
|
@required bool setToCurrent, |
|
}) { |
|
final scaffoldState = of(context); |
|
if (setToCurrent && |
|
tabIndex != scaffoldState.tabIndex && |
|
scaffoldState.mounted) { |
|
scaffoldState.setState(() { |
|
scaffoldState.tabIndex = tabIndex; |
|
}); |
|
} |
|
return scaffoldState.navStates[tabIndex].currentState; |
|
} |
|
|
|
static void navigateTo( |
|
BuildContext context, { |
|
@required int tabIndex, |
|
@required Widget widget, |
|
}) { |
|
tabNavigatorOf( |
|
context, |
|
tabIndex: tabIndex, |
|
setToCurrent: true, |
|
).push( |
|
MaterialPageRoute(builder: (context) => widget), |
|
); |
|
} |
|
} |
|
|
|
class _AppScaffoldState extends State<AppScaffold> { |
|
int tabIndex = 0; |
|
|
|
List<GlobalKey<NavigatorState>> navStates; |
|
|
|
@override |
|
void initState() { |
|
navStates = widget.pages.map((p) => GlobalKey<NavigatorState>()).toList(); |
|
super.initState(); |
|
} |
|
|
|
@override |
|
Widget build(BuildContext context) { |
|
return Scaffold( |
|
backgroundColor: Theme.of(context).primaryColor, |
|
body: IndexedStack( |
|
index: tabIndex, |
|
children: widget.pages |
|
.map( |
|
withIndex( |
|
(pageBuilder, index) => Navigator( |
|
key: navStates[index], |
|
onGenerateRoute: (route) => MaterialPageRoute( |
|
settings: route, |
|
builder: pageBuilder, |
|
), |
|
), |
|
), |
|
) |
|
.toList(), |
|
), |
|
bottomNavigationBar: BottomNavigationBar( |
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor, |
|
onTap: onTapNav, |
|
type: BottomNavigationBarType.fixed, |
|
currentIndex: tabIndex, |
|
items: widget.navItems, |
|
), |
|
); |
|
} |
|
|
|
void onTapNav(int index) { |
|
// double tap |
|
if (index == tabIndex) { |
|
navStates[index].currentState.popUntil((route) => route.isFirst); |
|
} else if (mounted) { |
|
setState(() { |
|
tabIndex = index; |
|
}); |
|
} |
|
} |
|
} |
|
|
|
|
|
/// helper for mapping with an index |
|
MapFn<A, B> withIndex<A, B>(IndexedMapFn<A, B> mapFn) { |
|
int index = -1; |
|
return (A a) => mapFn(a, ++index); |
|
} |
|
|
Very nice!