Created
July 30, 2018 15:19
-
-
Save rodydavis/932e9426b53468882717cf0f1b463a72 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2017, Spencer. All rights reserved. Use of this source code | |
// is governed by a BSD-style license that can be found in the LICENSE file. | |
import 'package:flutter/material.dart'; | |
import 'package:meta/meta.dart'; | |
typedef AppBar AppBarCallback(BuildContext context); | |
typedef void TextFieldSubmitCallback(String value); | |
typedef void SetStateCallback(void fn()); | |
class SearchBar { | |
/// Whether the search should take place "in the existing search bar", meaning whether it has the same background or a flipped one. Defaults to true. | |
final bool inBar; | |
bool isDarkTheme = false; | |
/// Whether the back button should be colored, if this is false the back button will be Colors.grey.shade400 | |
final bool colorBackButton; | |
/// Whether or not the search bar should close on submit. Defaults to true. | |
final bool closeOnSubmit; | |
/// Whether the text field should be cleared when it is submitted | |
final bool clearOnSubmit; | |
/// A callback which should return an AppBar that is displayed until search is started. One of the actions in this AppBar should be a search button which you obtain from SearchBar.getSearchAction(). This will be called every time search is ended, etc. (like a build method on a widget) | |
final AppBarCallback buildDefaultAppBar; | |
/// A void callback which takes a string as an argument, this is fired every time the search is submitted. Do what you want with the result. | |
final TextFieldSubmitCallback onSubmitted; | |
/// Since this should be inside of a State class, just pass setState to this. | |
final SetStateCallback setState; | |
/// Whether or not the search bar should add a clear input button, defaults to true. | |
final bool showClearButton; | |
/// What the hintText on the search bar should be. Defaults to 'Search'. | |
String hintText; | |
/// The controller to be used in the textField. | |
TextEditingController controller; | |
/// Whether search is currently active. | |
bool _isSearching = false; | |
/// Whether the clear button should be active (fully colored) or inactive (greyed out) | |
bool _clearActive = false; | |
/// The last built default AppBar used for colors and such. | |
AppBar _defaultAppBar; | |
VoidCallback clearSubmitOnPressed; | |
ValueChanged<String> onChanged; | |
SearchBar( | |
{@required this.setState, | |
@required this.buildDefaultAppBar, | |
this.onSubmitted, | |
this.clearSubmitOnPressed, | |
this.controller, | |
this.hintText = 'Search', | |
this.inBar = true, | |
this.onChanged, | |
this.colorBackButton = true, | |
this.closeOnSubmit = true, | |
this.clearOnSubmit = true, | |
this.isDarkTheme, | |
this.showClearButton = true}) { | |
if (this.controller == null) { | |
this.controller = TextEditingController(); | |
} | |
// Don't waste resources on listeners for the text controller if the dev | |
// doesn't want a clear button anyways in the search bar | |
if (!this.showClearButton) { | |
return; | |
} | |
this.controller.addListener(() { | |
if (this.controller.text.isEmpty) { | |
// If clear is already disabled, don't disable it | |
if (_clearActive) { | |
setState(() { | |
_clearActive = false; | |
}); | |
} | |
return; | |
} | |
// If clear is already enabled, don't enable it | |
if (!_clearActive) { | |
setState(() { | |
_clearActive = true; | |
}); | |
} | |
}); | |
} | |
/// Whether search is currently active. | |
bool get isSearching => _isSearching; | |
/// Initializes the search bar. | |
/// | |
/// This adds a route that listens for onRemove (and stops the search when that happens), and then calls [setState] to rebuild and start the search. | |
void beginSearch(context) { | |
ModalRoute.of(context).addLocalHistoryEntry(LocalHistoryEntry(onRemove: () { | |
setState(() { | |
_isSearching = false; | |
}); | |
})); | |
setState(() { | |
_isSearching = true; | |
}); | |
} | |
/// Builds, saves and returns the default app bar. | |
/// | |
/// This calls the [buildDefaultAppBar] provided in the constructor, and saves it to [_defaultAppBar]. | |
AppBar buildAppBar(BuildContext context) { | |
_defaultAppBar = buildDefaultAppBar(context); | |
return _defaultAppBar; | |
} | |
/// Builds the search bar! | |
/// | |
/// The leading will always be a back button. | |
/// backgroundColor is determined by the value of inBar | |
/// title is always a [TextField] with the key 'SearchBarTextField', and various text stylings based on [inBar]. This is also where [onSubmitted] has its listener registered. | |
/// | |
AppBar buildSearchBar(BuildContext context) { | |
ThemeData theme = Theme.of(context); | |
Color barColor = inBar ? _defaultAppBar.backgroundColor : theme.canvasColor; | |
// Don't provide a color (make it white) if it's in the bar, otherwise color it or set it to grey. | |
// Color buttonColor = inBar ? null : (colorBackButton ? _defaultAppBar.backgroundColor ?? theme.primaryColor ?? Colors.grey.shade400 : Colors.grey.shade400); | |
Color buttonColor = Colors.grey; | |
Color buttonDisabledColor = | |
inBar ? Color.fromRGBO(255, 255, 255, 0.25) : Colors.grey; | |
Color textColor = inBar ? Colors.white70 : Colors.black54; | |
return AppBar( | |
leading: BackButton(color: buttonColor), | |
backgroundColor: barColor, | |
title: Directionality( | |
textDirection: Directionality.of(context), | |
child: TextField( | |
key: Key('SearchBarTextField'), | |
keyboardType: TextInputType.text, | |
onChanged: onChanged, | |
style: TextStyle( | |
color: isDarkTheme ? null : textColor, fontSize: 16.0), | |
decoration: InputDecoration( | |
hintText: hintText, | |
hintStyle: TextStyle( | |
color: isDarkTheme ? null : textColor, fontSize: 16.0), | |
border: null), | |
onSubmitted: (String val) async { | |
if (closeOnSubmit) { | |
await Navigator.maybePop(context); | |
} | |
if (clearOnSubmit) { | |
controller.clear(); | |
} | |
onSubmitted(val); | |
}, | |
autofocus: true, | |
controller: controller, | |
)), | |
actions: !showClearButton | |
? <Widget>[ | |
IconButton( | |
icon: Icon(Icons.cancel), | |
color: Colors.grey, | |
onPressed: () async { | |
controller.clear(); | |
await Navigator.maybePop(context); | |
clearSubmitOnPressed(); | |
}, | |
) | |
] | |
: <Widget>[ | |
// Show an icon if clear is not active, so there's no ripple on tap | |
IconButton( | |
icon: Icon(Icons.clear, | |
color: _clearActive ? buttonColor : buttonDisabledColor), | |
disabledColor: buttonDisabledColor, | |
onPressed: !_clearActive | |
? null | |
: () { | |
controller.clear(); | |
}), | |
], | |
); | |
} | |
/// Returns an [IconButton] suitable for an Action | |
/// | |
/// Put this inside your [buildDefaultAppBar] method! | |
IconButton getSearchAction(BuildContext context) { | |
return IconButton( | |
icon: Icon(Icons.search), | |
onPressed: () { | |
beginSearch(context); | |
}); | |
} | |
/// Returns an AppBar based on the value of [_isSearching] | |
AppBar build(BuildContext context) { | |
return _isSearching ? buildSearchBar(context) : buildAppBar(context); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment