Files
packages.dart/deprecated/mobile_sidebar/lib/mobile_sidebar.dart
T
2026-01-15 17:36:22 -08:00

344 lines
12 KiB
Dart

import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
class TabChild {
final String title;
final Icon icon;
final WidgetBuilder builder;
TabChild({
@required this.title,
@required this.icon,
this.builder,
});
}
class MobileSidebar extends StatelessWidget {
final WidgetBuilder titleBuilder;
final WidgetBuilder accountBuilder;
final Widget Function(BuildContext context, ValueChanged<String> onChanged)
searchBarBuilder;
final WidgetBuilder ctaBuilder;
final WidgetBuilder menuButtonBuilder;
final WidgetBuilder searchIconBuilder;
final int currentIndex;
final Widget Function(
BuildContext context, List<String> titles, ValueChanged<int> onTap)
drawerBuilder;
final bool showSearchButton;
final List<TabChild> tabs;
final bool isSearching;
final ValueChanged<bool> isSearchChanged;
final ValueChanged<int> onTabChanged;
final ValueChanged<String> searchChanged;
final double tabWidth;
MobileSidebar({
Key key,
@required this.titleBuilder,
@required this.tabs,
@required this.onTabChanged,
@required this.currentIndex,
this.ctaBuilder,
this.tabWidth = 30.0,
this.accountBuilder,
this.searchBarBuilder,
this.searchChanged,
this.searchIconBuilder,
this.menuButtonBuilder,
this.drawerBuilder,
this.showSearchButton = false,
this.isSearching = false,
this.isSearchChanged,
}) : super(key: key);
final _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
const kMobileBreakpoint = 720.0;
final theme = Theme.of(context) ??
(Theme.of(context).brightness == Brightness.light
? ThemeData.light()
: ThemeData.dark());
return LayoutBuilder(
builder: (context, dimens) {
final _titles = tabs.map((t) => t.title).toList();
final _showDrawer = dimens.maxWidth <= kMobileBreakpoint;
return Scaffold(
key: _scaffoldKey,
drawer: _showDrawer
? drawerBuilder(context, _titles, onTabChanged)
: null,
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Material(
color: theme.appBarTheme.color,
elevation: theme.appBarTheme.elevation ?? 4.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
if (isSearching) ...[
if (_showDrawer) ...[
_menuIconBuilder(context)
] else ...[
Container(width: 8.0),
titleBuilder(context)
],
Expanded(
child: _buildSearchBar(),
),
IconButton(
icon: Icon(Icons.close),
onPressed: () => isSearchChanged(false),
),
] else ...[
Row(
children: <Widget>[
if (_showDrawer) ...[_menuIconBuilder(context)],
if (!_showDrawer) Container(width: 8.0),
titleBuilder(context),
],
),
if (!_showDrawer) ...[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: LayoutBuilder(
builder: (context, subDimens) {
final _titleCountToRemove = (subDimens.maxWidth /
(_titles.length * tabWidth))
.ceil();
List<String> _subTitles =
_titles.take(_titleCountToRemove).toList();
List<String> _extraTitles =
_titles.skip(_subTitles.length).toList();
final _selected = _titles[currentIndex];
if (_subTitles.indexOf(_selected) == -1) {
_subTitles = [_selected];
_extraTitles = _titles
.where((t) => t != _selected)
.toList();
}
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (var tab in _subTitles) ...[
_buildSubTab(_titles, tab, theme),
],
if (_subTitles.length != _titles.length) ...[
if (_extraTitles.length == 1) ...[
_buildSubTab(
_titles,
_extraTitles.first,
theme,
),
] else ...[
Container(width: 10.0),
Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Padding(
padding: EdgeInsets.only(
bottom: kBarHeight),
child: PopupMenuButton<String>(
child: Row(
children: <Widget>[
Text('More'),
Container(width: 4.0),
Icon(Icons.arrow_drop_down),
],
),
onSelected: (val) {
final _index =
_titles.indexOf(val);
onTabChanged(_index);
},
itemBuilder: (context) =>
_extraTitles
.map(
(t) => PopupMenuItem(
value: t,
child: Text(t),
),
)
.toList(),
),
),
),
),
]
],
],
);
},
),
),
)
],
Row(
children: <Widget>[
if (showSearchButton) ...[
_searchBuilder(context, _showDrawer),
],
ctaBuilder(context),
accountBuilder(context),
],
),
],
],
),
),
),
body: tabs[currentIndex].builder(context),
);
},
);
}
Widget _buildSubTab(List<String> _titles, String tab, ThemeData theme) {
return InkWell(
onTap: () {
final _index = _titles.indexOf(tab);
onTabChanged(_index);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: AutoSizeText(
tab,
style: theme.textTheme.bodyLarge.copyWith(
color: Colors.black,
),
),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 50),
height: kBarHeight,
width: tabWidth * 2,
color: tab == _titles[currentIndex]
? theme.colorScheme.secondary
: Colors.transparent,
),
],
),
),
);
}
final kBarHeight = 3.0;
Widget _menuIconBuilder(BuildContext context) {
return menuButtonBuilder(context);
return IconButton(
icon: Icon(Icons.menu),
onPressed: () => _scaffoldKey.currentState.openDrawer(),
);
}
Widget _searchBuilder(BuildContext context, bool showDrawer) {
if (showDrawer) {
return searchIconBuilder(context);
return IconButton(
icon: Icon(Icons.search),
onPressed: () {
isSearchChanged(!isSearching);
},
);
}
return searchBarBuilder(context, searchChanged);
const kSearchBarWidth = 225.0;
return Container(
width: kSearchBarWidth,
child: _buildSearchBar(),
);
}
Widget _buildSearchBar() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: isSearching ? 2.0 : 0.0,
child: TextField(
autofocus: isSearching,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
border: InputBorder.none,
filled: true,
hintText: "Search",
),
onTap: isSearching
? null
: () {
isSearchChanged(!isSearching);
},
),
),
);
}
Drawer _buildDrawer(BuildContext context, List<String> _titles) {
final _divider = Container(
height: 1.0,
color: Colors.grey[300],
);
return Drawer(
child: SafeArea(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () => Navigator.of(context).maybePop(),
),
Container(width: 8.0),
titleBuilder(context),
],
),
_divider,
Row(
children: <Widget>[
Expanded(
child: ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: _titles.length,
separatorBuilder: (context, index) {
return _divider;
},
itemBuilder: (context, index) {
return ListTile(
selected: index == currentIndex,
title: Text(_titles[index]),
onTap: () {
Navigator.of(context).maybePop();
onTabChanged(index);
},
);
},
),
),
],
),
_divider,
...[
ctaBuilder(context),
_divider,
],
],
),
),
),
);
}
}