diff --git a/example/lib/main.dart b/example/lib/main.dart index 456226fb..786b1543 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,27 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:getflutter/colors/gf_color.dart'; -import 'package:getflutter/components/button/gf_button.dart'; -import 'package:getflutter/components/badge/gf_button_badge.dart'; -import 'package:getflutter/components/avatar/gf_avatar.dart'; -import 'package:getflutter/components/tabs/gf_segment_tabs.dart'; import 'package:getflutter/getflutter.dart'; -import 'package:getflutter/shape/gf_button_shape.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:getflutter/components/toast/gf_toast.dart'; -import 'package:getflutter/components/list_tile/gf_list_tile.dart'; -import 'package:getflutter/components/button/gf_button_bar.dart'; -import 'package:getflutter/components/typography/gf_typography.dart'; -import 'package:getflutter/types/gf_typography_type.dart'; -import 'package:getflutter/components/toast/gf_floating_widget.dart'; -import 'package:getflutter/components/drawer/gf_drawer.dart'; -import 'package:getflutter/components/drawer/gf_drawer_header.dart'; -import 'package:getflutter/components/button/gf_icon_button.dart'; -import 'package:getflutter/components/image/gf_image_overlay.dart'; -import 'package:getflutter/components/card/gf_card.dart'; -import 'package:getflutter/components/tabs/gf_tabs.dart'; -import 'package:getflutter/components/tabs/gf_tabBarView.dart'; -import 'package:getflutter/types/gf_button_type.dart'; -import 'package:getflutter/position/gf_position.dart'; +import 'package:getflutter/components/search_bar/gf_search_bar.dart'; final List imageList = [ "https://cdn.pixabay.com/photo/2017/12/03/18/04/christmas-balls-2995437_960_720.jpg", @@ -81,6 +60,10 @@ class _MyHomePageState extends State Widget appBarTitle = new Text("UI Kit"); Icon actionIcon = new Icon(Icons.search); + List list = [ + "Flutter","Flutterjjk","Flutterhy","jhFlutter" + ]; + @override Widget build(BuildContext context) { return Scaffold( @@ -169,7 +152,7 @@ class _MyHomePageState extends State GFIconButton(icon: Icon(Icons.favorite), onPressed: null), ], ), - backgroundColor: Colors.blueGrey, +// backgroundColor: Colors.blueGrey, body: // GFTabBarView( // height: 200.0, @@ -190,93 +173,148 @@ class _MyHomePageState extends State // color: GFColor.secondary, ), - GFCard( - content: Column( - children: [ - GFTypography( - text: 'Toast', - type: GFTypographyType.typo6, - ), - SizedBox( - height: 20, - ), - SizedBox( - height: 20, - ), - GFToast( - text: 'Happy New Year', - button: GFButton( - onPressed: () { - print("dfr"); - }, - text: 'OK', - type: GFButtonType.outline, - color: GFColor.warning, - ), - ), - ], - ), - ), +// GFSearchBar( +// dataList: list, +// hideSearchBoxWhenItemSelected: false, +// listContainerHeight: MediaQuery.of(context).size.height / 4, +// queryBuilder: (query, list) { +// return list +// .where((item) => item.username +// .toLowerCase() +// .contains(query.toLowerCase())) +// .toList(); +// }, +// popupListItemBuilder: (item) { +// return item; +// }, +//// selectedItemBuilder: (selectedItem, deleteSelectedItem) { +//// return SelectedItemWidget(selectedItem, deleteSelectedItem); +//// }, +// // widget customization +// noItemsFoundWidget: Container(child: Text("fgv"),), +// textFieldBuilder: (controller, focusNode) { +// return Padding( +// padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), +// child: TextField( +// controller: controller, +// focusNode: focusNode, +// style: TextStyle(fontSize: 16, color: Colors.grey[600]), +// decoration: InputDecoration( +// enabledBorder: const OutlineInputBorder( +// borderSide: BorderSide( +// color: Color(0x4437474F), +// ), +// ), +// focusedBorder: OutlineInputBorder( +// borderSide: BorderSide(color: Theme.of(context).primaryColor), +// ), +// suffixIcon: Icon(Icons.search), +// border: InputBorder.none, +// hintText: "Search here...", +// contentPadding: const EdgeInsets.only( +// left: 16, +// right: 20, +// top: 14, +// bottom: 14, +// ), +// ), +// )); +// }, +// onItemSelected: (item) { +// setState(() { +//// _selectedItem = item; +// }); +// }, +// ), - GFCard( - content: Column( - children: [ - GFTypography( - text: 'Floating Toast', - type: GFTypographyType.typo6, - ), - GFFloatingWidget( - verticalPosition: 100, - child: showToast - ? GFToast( - width: 300, - text: 'Happy New Year', - button: GFButton( - onPressed: () { - print("df"); - }, - text: 'OK', - type: GFButtonType.outline, - color: GFColor.warning, - ), - ) - : Container(), - body: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - alignment: Alignment.center, - child: GFButton( - onPressed: () { - setState(() { - showToast = !showToast; - }); - }, - text: 'Click to View the toast', - type: GFButtonType.outline, - color: GFColor.warning, - ), - ) - ], - )) - ], +// GFCard( +// content: Column( +// children: [ +// GFTypography( +// text: 'Toast', +// type: GFTypographyType.typo6, +// ), +// SizedBox( +// height: 20, +// ), +// SizedBox( +// height: 20, +// ), +// GFToast( +// text: 'Happy New Year', +// button: GFButton( +// onPressed: () { +// print("dfr"); +// }, +// text: 'OK', +// type: GFButtonType.outline, +// color: GFColor.warning, +// ), +// ), +// ], +// ), +// ), +// +// GFCard( +// content: Column( +// children: [ +// GFTypography( +// text: 'Floating Toast', +// type: GFTypographyType.typo6, +// ), +// GFFloatingWidget( +// verticalPosition: 100, +// child: showToast +// ? GFToast( +// width: 300, +// text: 'Happy New Year', +// button: GFButton( +// onPressed: () { +// print("df"); +// }, +// text: 'OK', +// type: GFButtonType.outline, +// color: GFColor.warning, +// ), +// ) +// : Container(), +// body: Column( +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Container( +// alignment: Alignment.center, +// child: GFButton( +// onPressed: () { +// setState(() { +// showToast = !showToast; +// }); +// }, +// text: 'Click to View the toast', +// type: GFButtonType.outline, +// color: GFColor.warning, +// ), +// ) +// ], +// )) +// ], +// ), +// ), + + Container( + height: 130.0, + width: 105.0, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + gradient: LinearGradient( + begin: FractionalOffset.bottomLeft, + end: FractionalOffset.topRight, + colors: [ + const Color(0x5a0b486b), + const Color(0xFFF56217), + ]), ), ), -// Container( -// height: 130.0, -// width: 105.0, -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(8.0), -// gradient: LinearGradient( -// begin: FractionalOffset.bottomLeft, -// end: FractionalOffset.topRight, -// colors: [ -// const Color(0x5a0b486b), -// const Color(0xFFF56217), -// ])), -// ), -// // GFCard( // content: Column( @@ -545,9 +583,9 @@ class _MyHomePageState extends State children: [ GFImageOverlay( height: 200.0, - width: 200.0, + width: 304.0, image: AssetImage("lib/assets/food.jpeg"), - boxFit: BoxFit.fill, + boxFit: BoxFit.cover, colorFilter: new ColorFilter.mode( Colors.black.withOpacity(0.67), BlendMode.darken), // shape: BoxShape.circle, @@ -696,28 +734,28 @@ class _MyHomePageState extends State // backgroundImage: NetworkImage("https://cdn.pixabay.com/photo/2017/12/03/18/04/christmas-balls-2995437_960_720.jpg"), // ), - GFSegmentTabs( - tabController: tabController, - initialIndex: 0, - length: 3, - tabs: [ - Text( - "Tab1", - ), - Text( - "Tab2", - ), - Text( - "Tab3", - ), - ], - ), - - GFTabBarView(controller: tabController, children: [ - Container(color: Colors.red), - Container(color: Colors.green), - Container(color: Colors.blue) - ]), +// GFSegmentTabs( +// tabController: tabController, +// initialIndex: 0, +// length: 3, +// tabs: [ +// Text( +// "Tab1", +// ), +// Text( +// "Tab2", +// ), +// Text( +// "Tab3", +// ), +// ], +// ), +// +// GFTabBarView(controller: tabController, children: [ +// Container(color: Colors.red), +// Container(color: Colors.green), +// Container(color: Colors.blue) +// ]), // GFItemsCarousel( // rowCount: 3, @@ -735,36 +773,36 @@ class _MyHomePageState extends State // ).toList(), // ), // -// GFCarousel( -//// pagerSize: 12.0, -//// activeIndicator: Colors.pink, -//// passiveIndicator: Colors.pink.withOpacity(0.4), -//// viewportFraction: 1.0, -//// aspectRatio: 1.0, -//// autoPlay: true, -//// enlargeMainPage: true, -//// pagination: true, -// items: imageList.map( -// (url) { -// return Container( -// margin: EdgeInsets.all(8.0), -// child: ClipRRect( -// borderRadius: BorderRadius.all(Radius.circular(5.0)), -// child: Image.network( -// url, -// fit: BoxFit.cover, -// width: 1000.0 -// ), -// ), -// ); -// }, -// ).toList(), -// onPageChanged: (index) { -// setState(() { -// index; -// }); -// }, -// ), + GFCarousel( +// pagerSize: 12.0, +// activeIndicator: Colors.pink, +// passiveIndicator: Colors.pink.withOpacity(0.4), +// viewportFraction: 1.0, +// aspectRatio: 1.0, +// autoPlay: true, +// enlargeMainPage: true, +// pagination: true, + items: imageList.map( + (url) { + return Container( + margin: EdgeInsets.all(8.0), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(5.0)), + child: Image.network( + url, + fit: BoxFit.cover, + width: 1000.0 + ), + ), + ); + }, + ).toList(), + onPageChanged: (index) { + setState(() { + index; + }); + }, + ), GFTabs( initialIndex: 0, @@ -842,42 +880,41 @@ class _MyHomePageState extends State // ), GFCard( + gradient: LinearGradient( + begin: FractionalOffset.bottomLeft, + end: FractionalOffset.topRight, + colors: [ + const Color(0x5a0b486b), + const Color(0xFFF56217), + ]), boxFit: BoxFit.fill, - colorFilter: new ColorFilter.mode( - Colors.black.withOpacity(0.67), BlendMode.darken), - image: Image.asset( - "lib/assets/img.png", - fit: BoxFit.fitWidth, - width: 400.0, - ), + colorFilter: new ColorFilter.mode(Colors.black.withOpacity(0.67), BlendMode.darken), +// image: Image.asset( +// "lib/assets/img.png", +// fit: BoxFit.fitWidth, +// width: 400.0, +// ), // imageOverlay: AssetImage("lib/assets/food.jpeg"), titlePosition: GFPosition.end, title: GFListTile( avatar: GFAvatar( + backgroundColor: Color(0x5a0b486b), child: Text("tb"), ), - title: Text( - 'title', - style: TextStyle(color: Colors.grey), - ), - subTitle: Text( - 'subtitle' - "Flutter Flutter is Google's mobile UI framework for crafting" - "Flutter Flutter is Google's mobile UI framework for crafting", - style: TextStyle(color: Colors.grey), - ), + title: Text('Flutter',), + subTitle: Text("Flutter is Google's mobile UI",), + description: Text("Flutter Flutter is Google's mobile UI framework for crafting"), icon: GFIconButton( onPressed: null, icon: Icon(Icons.favorite), type: GFButtonType.transparent, ), ), +// borderOnForeground: true, content: Text( - "Flutter Flutter is Google's mobile UI framework for crafting" "Flutter Flutter is Google's mobile UI framework for crafting" "Flutter Flutter is Google's mobile UI framework for crafting" "Flutter Flutter is Google's mobile UI framework for crafting", - style: TextStyle(color: Colors.grey), ), buttonBar: GFButtonBar( children: [ diff --git a/lib/components/card/gf_card.dart b/lib/components/card/gf_card.dart index 01ac1176..acd4affe 100644 --- a/lib/components/card/gf_card.dart +++ b/lib/components/card/gf_card.dart @@ -32,9 +32,14 @@ class GFCard extends StatelessWidget { this.border, this.boxFit, this.colorFilter, - this.height}) + this.height, + this.gradient}) : assert(elevation == null || elevation >= 0.0), assert(borderOnForeground != null), + assert(color == null || gradient == null, + 'Cannot provide both a color and a decoration\n' + 'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".' + ), super(key: key); /// defines the card's height @@ -99,6 +104,8 @@ class GFCard extends StatelessWidget { /// A border to draw above the [GFCard]. final Border border; + final LinearGradient gradient; + static const double _defaultElevation = 1.0; static const Clip _defaultClipBehavior = Clip.none; @@ -142,19 +149,23 @@ class GFCard extends StatelessWidget { return Container( height: height, margin: margin ?? cardTheme.margin ?? const EdgeInsets.all(16.0), - child: Material( + decoration: gradient != null ? BoxDecoration( + gradient: gradient, + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ) : null, + child: gradient == null ? Material( type: MaterialType.card, color: color ?? cardTheme.color ?? Theme.of(context).cardColor, - elevation: elevation ?? cardTheme.elevation ?? _defaultElevation, - shape: shape ?? - cardTheme.shape ?? - const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0)), - ), - borderOnForeground: borderOnForeground, - clipBehavior: - clipBehavior ?? cardTheme.clipBehavior ?? _defaultClipBehavior, - child: imageOverlay == null ? cardChild : overlayImage), + elevation: elevation ?? cardTheme.elevation ?? _defaultElevation, + shape: shape ?? + cardTheme.shape ?? + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + borderOnForeground: borderOnForeground, + clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? _defaultClipBehavior, + child: imageOverlay == null ? cardChild : overlayImage, + ) : imageOverlay == null ? cardChild : overlayImage, ); } } diff --git a/lib/components/search_bar/gf_search_bar.dart b/lib/components/search_bar/gf_search_bar.dart index 8b137891..b3dc4926 100644 --- a/lib/components/search_bar/gf_search_bar.dart +++ b/lib/components/search_bar/gf_search_bar.dart @@ -1 +1,300 @@ +import 'package:flutter/material.dart'; +typedef QueryListItemBuilder = Widget Function(T item); +typedef OnItemSelected = void Function(T item); +typedef SelectedItemBuilder = Widget Function( + T item, + VoidCallback deleteSelectedItem, + ); +typedef QueryBuilder = List Function( + String query, + List list, + ); +typedef TextFieldBuilder = Widget Function( + TextEditingController controller, + FocusNode focus, + ); + +class GFSearchBar extends StatefulWidget { + const GFSearchBar({ + @required this.dataList, + @required this.popupListItemBuilder, + @required this.selectedItemBuilder, + @required this.queryBuilder, + Key key, + this.onItemSelected, + this.hideSearchBoxWhenItemSelected = false, + this.listContainerHeight, + this.noItemsFoundWidget, + this.textFieldBuilder, + }) : super(key: key); + + final List dataList; + final QueryListItemBuilder popupListItemBuilder; + final SelectedItemBuilder selectedItemBuilder; + final bool hideSearchBoxWhenItemSelected; + final double listContainerHeight; + final QueryBuilder queryBuilder; + final TextFieldBuilder textFieldBuilder; + final Widget noItemsFoundWidget; + + final OnItemSelected onItemSelected; + + @override + MySingleChoiceSearchState createState() => MySingleChoiceSearchState(); +} + +class MySingleChoiceSearchState extends State> { + final _controller = TextEditingController(); + List _list; + List _tempList; + bool isFocused; + FocusNode _focusNode; + ValueNotifier notifier; + bool isRequiredCheckFailed; + Widget textField; + OverlayEntry overlayEntry; + bool showTextBox = false; + double listContainerHeight; + final LayerLink _layerLink = LayerLink(); + final double textBoxHeight = 48; + final TextEditingController textController = TextEditingController(); + + @override + void initState() { + super.initState(); + init(); + } + + void init() { + _tempList = []; + notifier = ValueNotifier(null); + _focusNode = FocusNode(); + isFocused = false; + _list = List.from(widget.dataList); + _tempList.addAll(_list); + _focusNode.addListener(() { + if (!_focusNode.hasFocus) { + _controller.clear(); + if (overlayEntry != null) { + overlayEntry.remove(); + } + overlayEntry = null; + } else { + _tempList + ..clear() + ..addAll(_list); + if (overlayEntry == null) { + onTap(); + } else { + overlayEntry.markNeedsBuild(); + } + } + }); + _controller.addListener(() { + final text = _controller.text; + if (text.trim().isNotEmpty) { + _tempList.clear(); + final filterList = widget.queryBuilder(text, widget.dataList); + if (filterList == null) { + throw Exception( + "Filtered List cannot be null. Pass empty list instead", + ); + } + _tempList.addAll(filterList); + if (overlayEntry == null) { + onTap(); + } else { + overlayEntry.markNeedsBuild(); + } + } else { + _tempList + ..clear() + ..addAll(_list); + if (overlayEntry == null) { + onTap(); + } else { + overlayEntry.markNeedsBuild(); + } + } + }); +// KeyboardVisibilityNotification().addNewListener( +// onChange: (visible) { +// if (!visible) { +// _focusNode.unfocus(); +// } +// }, +// ); + } + + @override + void didUpdateWidget(GFSearchBar oldWidget) { + if (oldWidget.dataList != widget.dataList) { + init(); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + listContainerHeight = + widget.listContainerHeight ?? MediaQuery.of(context).size.height / 4; + textField = widget.textFieldBuilder != null + ? widget.textFieldBuilder(_controller, _focusNode) + : Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: TextField( + controller: _controller, + focusNode: _focusNode, + style: TextStyle(fontSize: 16, color: Colors.grey[600]), + decoration: InputDecoration( + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x4437474F), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).primaryColor, + ), + ), + suffixIcon: Icon(Icons.search), + border: InputBorder.none, + hintText: "Search here...", + contentPadding: const EdgeInsets.only( + left: 16, + right: 20, + top: 14, + bottom: 14, + ), + ), + ), + ); + + final column = Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.hideSearchBoxWhenItemSelected && notifier.value != null) + const SizedBox(height: 0) + else + CompositedTransformTarget( + link: _layerLink, + child: textField, + ), + if (notifier.value != null) + Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.all(Radius.circular(4)), + ), + margin: const EdgeInsets.symmetric(horizontal: 16), + child: widget.selectedItemBuilder( + notifier.value, + onDeleteSelectedItem, + ), + ), + ], + ); + return column; + } + + void onDropDownItemTap(T item) { + if (overlayEntry != null) { + overlayEntry.remove(); + } + overlayEntry = null; + _controller.clear(); + _focusNode.unfocus(); + setState(() { + notifier.value = item; + isFocused = false; + isRequiredCheckFailed = false; + }); + if (widget.onItemSelected != null) { + widget.onItemSelected(item); + } + } + + void onTap() { + final RenderBox textFieldRenderBox = context.findRenderObject(); + final RenderBox overlay = Overlay.of(context).context.findRenderObject(); + final width = textFieldRenderBox.size.width; + final position = RelativeRect.fromRect( + Rect.fromPoints( + textFieldRenderBox.localToGlobal( + textFieldRenderBox.size.topLeft(Offset.zero), + ancestor: overlay, + ), + textFieldRenderBox.localToGlobal( + textFieldRenderBox.size.topRight(Offset.zero), + ancestor: overlay, + ), + ), + Offset.zero & overlay.size, + ); + overlayEntry = OverlayEntry( + builder: (context) { + final height = MediaQuery.of(context).size.height; + return Positioned( + left: position.left, + width: width, + child: CompositedTransformFollower( + offset: Offset( + 0, + height - position.bottom < listContainerHeight + ? (textBoxHeight + 6.0) + : -(listContainerHeight - 8.0), + ), + showWhenUnlinked: false, + link: _layerLink, + child: Container( + height: listContainerHeight, + margin: const EdgeInsets.symmetric(horizontal: 12), + child: Card( + color: Colors.white, + elevation: 5, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + child: _tempList.isNotEmpty + ? Scrollbar( + child: ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 4), + separatorBuilder: (context, index) => const Divider( + height: 1, + ), + itemBuilder: (context, index) => Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onDropDownItemTap(_tempList[index]), + child: widget.popupListItemBuilder( + _tempList.elementAt(index), + ), + ), + ), + itemCount: _tempList.length, + ), + ) + : widget.noItemsFoundWidget != null + ? Center( + child: widget.noItemsFoundWidget, + ) : Container( + child: Text("no items found"), + ), + ), + ), + ), + ); + }, + ); + Overlay.of(context).insert(overlayEntry); + } + + void onDeleteSelectedItem() { + setState(() => notifier.value = null); + if (widget.onItemSelected != null) { + widget.onItemSelected(null); + } + } +}