283 lines
8.6 KiB
Dart
283 lines
8.6 KiB
Dart
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<bool> streamController = StreamController<bool>.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<WorkoutMenuTree> 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<WorkoutMenuTree> 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<AnimatedSearch> {
|
|
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<WorkoutMenuTree> 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<WorkoutMenuTree>(
|
|
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<AnimateExpansion> with SingleTickerProviderStateMixin {
|
|
late AnimationController _animationController;
|
|
late Animation<double> _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();
|
|
}
|
|
}
|