import 'dart:async'; import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/library/dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class SearchBarStream { static final SearchBarStream _singleton = SearchBarStream._internal(); final StreamController streamController = StreamController.broadcast(); bool searching = false; Stream get stream => streamController.stream; StreamController getStreamController() => streamController; factory SearchBarStream() => _singleton; SearchBarStream._internal(); void dispose() { streamController.close(); } } // ignore: must_be_immutable class MenuSearchBar extends StatelessWidget { final List listItems; final Function(WorkoutMenuTree?) onFind; bool showIcon; MenuSearchBar({required this.listItems, required this.onFind, this.showIcon = true}); @override Widget build(BuildContext context) { return AnimatedSearch( listItems: listItems, onFind: onFind, showIcon: showIcon, ); } } // ignore: must_be_immutable class AnimatedSearch extends StatefulWidget { final List listItems; final Function(WorkoutMenuTree?) onFind; bool showIcon = true; AnimatedSearch({required this.listItems, required this.onFind, required this.showIcon}); @override _AnimatedSearch createState() => _AnimatedSearch(); } class _AnimatedSearch extends State { bool isSearching = false; final Stream stream = SearchBarStream().stream; var subscription; @override void initState() { super.initState(); } @override void didChangeDependencies() { subscription = stream.listen((value) { setState(() { isSearching = SearchBarStream().searching; }); }); super.didChangeDependencies(); } @override void dispose() { subscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.only(top: 0), child: Stack( alignment: Alignment.center, children: [ AnimateExpansion( animate: widget.showIcon ? !isSearching : false, axisAlignment: 1.0, child: IconButton( onPressed: () => { setState(() { isSearching = !isSearching; SearchBarStream().searching = isSearching; SearchBarStream().getStreamController().add(true); }) }, icon: Icon( Icons.search, color: Color(0xffb4f500), size: 40, ), )), AnimateExpansion( animate: widget.showIcon ? isSearching : true, axisAlignment: -1.0, child: Search( listItems: widget.listItems, onFind: widget.onFind, ), ), ], )); } } // ignore: must_be_immutable class Search extends StatelessWidget with Trans { final List listItems; final Function(WorkoutMenuTree?) onFind; Search({required this.listItems, required this.onFind}); @override Widget build(BuildContext context) { setContext(context); return SizedBox( width: MediaQuery.of(context).size.width * .6, child: DropdownSearch( onPopupDismissed: () => {SearchBarStream().searching = false, SearchBarStream().getStreamController().add(false)}, showAsSuffixIcons: false, showSearchBox: true, mode: Mode.DIALOG, showSelectedItem: false, items: listItems, onChanged: (value) => onFind(value), dropdownSearchDecoration: InputDecoration( contentPadding: EdgeInsets.only(left: 15, top: 0, bottom: 5), labelStyle: GoogleFonts.inter(fontSize: 12, color: Colors.yellow[50]), fillColor: Colors.black38, filled: true, border: OutlineInputBorder( gapPadding: 8.0, borderRadius: BorderRadius.circular(12.0), borderSide: BorderSide(color: Colors.white12, width: 0.4), ), ), searchBoxController: TextEditingController(), searchBoxDecoration: InputDecoration( contentPadding: EdgeInsets.only(left: 15, top: 5, bottom: 5), labelText: t("Search Exercises..."), labelStyle: GoogleFonts.inter(fontSize: 12, color: Colors.grey[400]), fillColor: Colors.white24, filled: true, border: OutlineInputBorder( gapPadding: 8.0, borderRadius: BorderRadius.circular(12.0), borderSide: BorderSide(color: Colors.white12, width: 0.4), ), ), popupBackgroundColor: Colors.grey[700], popupItemBuilder: (context, item, isSelected) { return Container( margin: EdgeInsets.all(3), decoration: BoxDecoration( image: DecorationImage( image: AssetImage('asset/image/WT_black_G_background.jpg'), fit: BoxFit.cover, ), ), child: ListTile( selected: isSelected, title: Text( item!.name, style: GoogleFonts.inter(color: Colors.yellow[300], fontSize: 16), ), subtitle: Text( item.parentName, maxLines: 1, style: GoogleFonts.inter(color: Colors.grey[400]), ), leading: ClipRRect( borderRadius: BorderRadius.circular(8.0), child: Image.asset(item.imageName), ), ), ); }, emptyBuilder: (context, searchEntry) => Center( child: Text( t("No exercise found"), textAlign: TextAlign.center, style: GoogleFonts.inter(color: Colors.yellow[200], fontSize: 16), )), dropdownBuilder: (context, WorkoutMenuTree? item, itemDesignation) => Container( child: ListView(scrollDirection: Axis.vertical, shrinkWrap: true, children: [ (item == null) ? Container( height: 15, padding: EdgeInsets.all(0), child: Text( t("Search Exercises..."), style: GoogleFonts.inter(color: Colors.grey[400], fontSize: 14), ), ) : Container( height: 15, padding: EdgeInsets.all(0), child: Text( item.name, maxLines: 3, style: GoogleFonts.inter(color: Colors.yellow[400], fontSize: 14), ), ), ]))), ); } } class AnimateExpansion extends StatefulWidget { final Widget child; final bool animate; final double axisAlignment; AnimateExpansion({ this.animate = false, required this.axisAlignment, required this.child, }); @override _AnimateExpansionState createState() => _AnimateExpansionState(); } class _AnimateExpansionState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _animation; void prepareAnimations() { _animationController = AnimationController( vsync: this, duration: Duration(milliseconds: 350), ); _animation = CurvedAnimation( parent: _animationController, curve: Curves.easeInCubic, reverseCurve: Curves.easeOutCubic, ); } void _toggle() { if (widget.animate) { _animationController.forward(); } else { _animationController.reverse(); } } @override void initState() { super.initState(); prepareAnimations(); _toggle(); } @override void didUpdateWidget(AnimateExpansion oldWidget) { super.didUpdateWidget(oldWidget); _toggle(); } @override Widget build(BuildContext context) { return SizeTransition(axis: Axis.horizontal, axisAlignment: -1.0, sizeFactor: _animation, child: widget.child); } @override void dispose() { _animationController.dispose(); super.dispose(); } }