Unable to empty stored string of a variable after textEditingController.clear() - flutter

I am trying to build an animated search widget. When I use this widget in a different screen, I am storing the text of the textEditingController searchController of this search widget in a String variable searchText. The searchText variable is updating fine when I assign it the value of the searchController.text during the onChanged function call of the widget. However, when I click on the icon button of the search widget, I am clearing the searchController but it is not updating the value of the searchText variable. What I understand is that the clear() action is not a part of the onChanged() call.
How do I update the value of the searchText variable when the searchController.clear() is called in the onPressed call of the icon button? I am implementing Tween animation in the onPressed function of the icon button, so I am not sure if I can/should be using it to update the value of the string variable.
My Code
girls_screen.dart
import 'package:animation_search_bar/animation_search_bar.dart';
import 'package:flutter/material.dart';
import 'package:switch_circle_color/model/girls.dart';
import 'package:switch_circle_color/screens/gender.dart';
import 'package:switch_circle_color/screens/girls_cart.dart';
import 'package:switch_circle_color/screens/search_widget.dart';
import 'package:switch_circle_color/screens/selected_girl_details.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import '../global_variables/global_variables.dart';
int distance = 15;
int age = 25;
String sortCriterion = "distance";
bool isSingle = false;
bool isSerious = false;
int count = 0;
String searchText = "";
List<Girls> allGirls = [
Girls("Reshmita", 25, 33, "Married", "Serious"),
Girls("Ankita", 17, 26, "Single", "Serious"),
Girls("Rupali", 42, 28, "Single", "Casual"),
Girls("Monica", 50, 24, "Single", "Casual"),
Girls("Sakshi", 9, 27, "Married", "Casual"),
];
List<Girls> filteredGirlsbyDistance = [];
List<Girls> filteredGirlsbyAge = [];
List<Girls> filteredGirls = [];
List<Girls> filteredGirlsbySerious = [];
List<Girls> filteredGirlsbySingle = [];
//List<Girls> girlsCart = [];
void addGirlToCart(Girls girl) {
girlsCart.add(girl);
cartValue = cartValue + girl.age;
}
void removeGirlsfromCart(Girls girl) {
girlsCart.remove(girl);
cartValue = cartValue - girl.age;
}
String selectedGirlName = "";
int? selectedGirlDistance;
void populateFilteredGirls(int dis, int ag) {
filteredGirlsbyDistance.clear();
filteredGirlsbyAge.clear();
filteredGirlsbySingle.clear();
filteredGirlsbySerious.clear();
filteredGirls.clear();
//int len = filteredGirls.length;
for (int i = 0; i < allGirls.length; i++) {
if (allGirls[i].distance <= dis) {
filteredGirlsbyDistance.add(allGirls[i]);
}
}
filteredGirls = filteredGirlsbyDistance;
for (int i = 0; i < filteredGirls.length; i++) {
if (filteredGirls[i].age <= ag) {
filteredGirlsbyAge.add(filteredGirls[i]);
}
}
filteredGirls = filteredGirlsbyAge;
//len = filteredGirls.length;
if (isSingle == true) {
for (int i = 0; i < filteredGirls.length; i++) {
if (filteredGirls[i].status.toLowerCase() == "single") {
filteredGirlsbySingle.add(filteredGirls[i]);
}
}
filteredGirls = filteredGirlsbySingle;
}
if (isSerious == true) {
for (int i = 0; i < filteredGirls.length; i++) {
if (filteredGirls[i].lookingFor.toLowerCase() == "serious") {
filteredGirlsbySerious.add(filteredGirls[i]);
}
}
filteredGirls = filteredGirlsbySerious;
}
//filteredGirls = filteredGirls.toSet().toList();
}
class GirlsResultGrid extends StatefulWidget {
GirlsResultGrid({Key? key}) : super(key: key);
#override
State<GirlsResultGrid> createState() => _GirlsResultGridState();
}
class _GirlsResultGridState extends State<GirlsResultGrid> {
//late final GlobalKey<AnimationLimiterState> _key
#override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: AnimationLimiter(
key: ValueKey("list $count"),
child: GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
childAspectRatio: 2,
children: List.generate(filteredGirls.length, (index) {
return AnimationConfiguration.staggeredGrid(
columnCount: 2,
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
child: FadeInAnimation(
child: ListTile(
leading: InkWell(
child: const Icon(Icons.girl_outlined),
onTap: () {
removeGirlsfromCart(filteredGirls[index]);
},
),
trailing: InkWell(
child: Text("${filteredGirls[index].distance} km away"),
onTap: () {
addGirlToCart(filteredGirls[index]);
},
),
title: Text("${filteredGirls[index].name}"),
subtitle: Text(
"${filteredGirls[index].age} years old, ${filteredGirls[index].status}, ${filteredGirls[index].lookingFor}"),
onTap: () {
// setState(() {
// selectedGirlName = filteredGirls[index].name;
// selectedGirlDistance = filteredGirls[index].distance;
// });
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SelectedGirlDetails(
girl: filteredGirls[index],
)));
},
),
),
),
);
}),
//itemCount: ,
//itemBuilder: (BuildContext context, int index) {
),
),
),
);
}
}
class GirlsScreen extends StatefulWidget {
GirlsScreen({Key? key}) : super(key: key);
#override
State<GirlsScreen> createState() => _GirlsScreenState();
}
class _GirlsScreenState extends State<GirlsScreen> {
TextEditingController searchTextEditingController = TextEditingController();
void changeDistance(double num) {
setState(() {
distance = num.round();
populateFilteredGirls(distance, age);
count++;
});
}
void changeAgeLimit(double a) {
setState(() {
age = a.round();
populateFilteredGirls(distance, age);
count++;
});
}
#override
void initState() {
super.initState();
//filteredGirls = allGirls;
distance = 20;
age = 28;
sortCriterion = "distance";
isSingle = false;
isSerious = false;
(sortCriterion == "distance")
? allGirls.sort((a, b) => a.distance.compareTo(b.distance))
: allGirls.sort((a, b) => a.age.compareTo(b.age));
populateFilteredGirls(distance, age);
}
// Widget buildResult(BuildContext context) {
// }
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Padding(padding: EdgeInsets.all(10)),
// ElevatedButton(
// onPressed: () {
// Navigator.push(context,
// MaterialPageRoute(builder: (context) => GenderScreen()));
// },
// child: Text("Go to Gender Screen")),
// Padding(padding: EdgeInsets.all(30)),
SearchWidget(
searchController: searchTextEditingController,
onChanged: (p0) {
setState(() {
searchText = p0;
});
},
),
Text("Girl searched is: $searchText"),
Padding(padding: EdgeInsets.all(10)),
Text("Set max distance"),
Padding(padding: EdgeInsets.all(10)),
Slider(
min: 1.0,
max: 100.0,
divisions: 100,
activeColor: Colors.green,
inactiveColor: Colors.orange,
label: 'Set distance value',
value: distance.toDouble(),
onChanged: (value) {
changeDistance(value);
},
),
Padding(padding: EdgeInsets.all(10)),
Text("Current distance is $distance kms"),
Padding(padding: EdgeInsets.all(10)),
Text("Set max age"),
Padding(padding: EdgeInsets.all(10)),
Slider(
min: 18.0,
max: 60.0,
divisions: 42,
activeColor: Colors.green,
inactiveColor: Colors.orange,
label: 'Set age limit',
value: age.toDouble(),
onChanged: (value) {
changeAgeLimit(value);
},
),
Padding(padding: EdgeInsets.all(10)),
Text("Age limit is $age years"),
Padding(padding: EdgeInsets.all(10)),
Text("Sort by:"),
Padding(padding: EdgeInsets.all(7.5)),
ListTile(
//minLeadingWidth: 30,
title: Text("Age"),
leading: Radio(
value: "age",
groupValue: sortCriterion,
onChanged: (value) {
setState(() {
sortCriterion = value.toString();
allGirls.sort((a, b) => a.age.compareTo(b.age));
populateFilteredGirls(distance, age);
count++;
});
},
),
),
Padding(padding: EdgeInsets.all(7.5)),
ListTile(
//minLeadingWidth: 30,
title: Text("Distance"),
leading: Radio(
value: "distance",
groupValue: sortCriterion,
onChanged: (value) {
setState(() {
sortCriterion = value.toString();
allGirls.sort((a, b) => a.distance.compareTo(b.distance));
populateFilteredGirls(distance, age);
count++;
});
},
),
),
Padding(padding: EdgeInsets.all(10)),
Text("Is Single?"),
Padding(padding: EdgeInsets.all(2.5)),
InkWell(
onTap: () {
setState(() {
isSingle = !isSingle;
populateFilteredGirls(distance, age);
count++;
});
},
child: (isSingle == false)
? Icon(Icons.check_box_outline_blank)
: Icon(Icons.check_box),
),
Padding(padding: EdgeInsets.all(5)),
Text("Is Serious?"),
Padding(padding: EdgeInsets.all(2.5)),
InkWell(
onTap: () {
setState(() {
isSerious = !isSerious;
populateFilteredGirls(distance, age);
count++;
});
},
child: (isSerious == false)
? Icon(Icons.check_box_outline_blank)
: Icon(Icons.check_box),
),
Padding(padding: EdgeInsets.all(10)),
//buildResult(context),
GirlsResultGrid(),
Padding(padding: EdgeInsets.all(25)),
ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => GirlsCart()));
},
child: Text("Go to Girls Cart")),
Padding(padding: EdgeInsets.all(25)),
],
),
));
}
}
search_widget.dart
import 'package:flutter/material.dart';
class SearchWidget extends StatefulWidget {
Function(String)? onChanged;
TextEditingController? searchController;
VoidCallback? onTap;
SearchWidget({
Key? key,
this.onChanged,
this.searchController,
this.onTap,
}) : super(key: key);
#override
State<SearchWidget> createState() => _SearchWidgetState();
}
class _SearchWidgetState extends State<SearchWidget>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController animController;
bool isForward = false;
//TextEditingController searchController = TextEditingController();
#override
void initState() {
// TODO: implement initState
super.initState();
animController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
final curvedAnimation =
CurvedAnimation(parent: animController, curve: Curves.easeOutExpo);
animation = Tween<double>(begin: 0, end: 150).animate(curvedAnimation)
..addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Material(
child: Padding(
padding: const EdgeInsets.all(25.0),
child: Container(
width: 220,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
width: animation.value,
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(50),
bottomLeft: Radius.circular(50),
),
),
child: Padding(
padding: EdgeInsets.only(left: 20, bottom: 5),
child: TextField(
//autofocus: true,
controller: widget.searchController,
onChanged: widget.onChanged,
cursorColor: Colors.white54,
style: TextStyle(color: Colors.white54, fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Search girls...",
hintStyle: TextStyle(color: Colors.white30),
),
),
),
),
Container(
width: 50,
height: 53,
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: animation.value > 1
? BorderRadius.only(
topLeft: Radius.circular(0),
bottomRight: Radius.circular(50),
bottomLeft: Radius.circular(0),
topRight: Radius.circular(50),
)
: BorderRadius.circular(50),
),
child: IconButton(
onPressed: () {
if (!isForward) {
animController.forward();
isForward = true;
} else if (isForward &&
widget.searchController!.text != "") {
widget.searchController!.clear();
} else {
animController.reverse();
isForward = false;
}
},
icon: (!isForward)
? Icon(
Icons.search,
color: Colors.white54,
)
: Icon(
Icons.close_rounded,
color: Colors.white54,
),
),
),
],
),
),
),
);
}
}
Video for reference
https://vimeo.com/735839961

Realized the solution. I added a Voidcallback parameter to the widget, which is called once the cross icon button is tapped on.
In girls_screen.dart
SearchWidget(
searchController: searchTextEditingController,
onChanged: (p0) {
setState(() {
searchText = p0;
});
},
onTap: () {
if (searchText != "") {
setState(() {
searchText = "";
});
}
},
),
In search_widget.dart
child: IconButton(
onPressed: () {
if (!isForward) {
animController.forward();
isForward = true;
} else if (isForward &&
widget.searchController!.text != "") {
widget.searchController!.clear();
} else {
animController.reverse();
isForward = false;
}
widget.onTap!();
},

Related

How can I solve this problem that two keyboard cursors in textfield?

https://i.stack.imgur.com/YKClg.png
I have multiple textfields and I have written code that expects that clearing index 0, i.e. the first textfield in the list, will set the focus back to the first textfield, but contrary to expectations, the cursor moves to the second field. text field. , and why do I see two cursors when I click on another textfield in this state?
`
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:todolist/dataForm.dart';
class TodoCase extends StatefulWidget {
final String title;
const TodoCase({
Key? key,
required this.title,
}) : super(key: key);
#override
State<TodoCase> createState() => _TodoCaseState();
}
class _TodoCaseState extends State<TodoCase> {
List<ToDoDataForm> toDoLineList = [];
final FocusScopeNode textListScopeNode = FocusScopeNode();
final ScrollController toDoScrollController = ScrollController();
List<TextEditingController> textEditController = [];
List<FocusNode> keyBoardFocusNode = [];
List<FocusNode> textFocusNode = [];
Future<void> scrollToEnd() async {
await Future.delayed(const Duration(milliseconds: 100));
toDoScrollController.animateTo(
toDoScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 400),
curve: Curves.ease,
);
}
#override
Widget build(BuildContext context) {
void addList() {
setState(() {
toDoLineList.add(ToDoDataForm(todoData: ""));
scrollToEnd();
});
}
Widget renderTextFormField(ToDoDataForm dataForm, int index) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(
value: dataForm.isDone,
onChanged: (value) {
setState(() {
dataForm.isDone = value!;
});
}),
Flexible(
child: KeyboardListener(
focusNode: keyBoardFocusNode[index],
onKeyEvent: (value) {
if (value.logicalKey == LogicalKeyboardKey.backspace &&
dataForm.todoData.isEmpty) {
setState(() {
if (index != 0) {
textFocusNode[index - 1].requestFocus();
toDoLineList.removeAt(index);
textFocusNode.removeAt(index);
keyBoardFocusNode.removeAt(index);
textEditController.removeAt(index);
} else if (index == 0 && toDoLineList.length == 1) {
toDoLineList.removeAt(index);
textFocusNode.removeAt(index);
textEditController.removeAt(index);
keyBoardFocusNode.removeAt(index);
} else if (index == 0 && toDoLineList.length != 1) {
FocusScope.of(context)
.requestFocus(textFocusNode[index + 1]);
toDoLineList.removeAt(index);
textFocusNode.removeAt(index);
textEditController.removeAt(index);
keyBoardFocusNode.removeAt(index);
}
});
print(textFocusNode);
}
},
child: TextField(
decoration: const InputDecoration(
border: InputBorder.none,
),
autofocus: false,
controller: textEditController[index],
focusNode: textFocusNode[index],
onChanged: (value) {
//print('edit : $index');
dataForm.todoData = value;
//print(textFocusNode);
},
),
),
)
],
);
}
return SizedBox(
width: 180,
height: 200,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.title,
style: const TextStyle(
color: Colors.black,
fontSize: 24,
),
),
IconButton(
onPressed: addList,
icon: const Icon(Icons.add),
padding: EdgeInsets.zero,
iconSize: 20,
),
],
),
Container(
height: 1,
color: Colors.black,
),
Expanded(
child: ListView.builder(
itemCount: toDoLineList.length,
controller: toDoScrollController,
itemBuilder: ((context, index) {
if (textEditController.length - 1 < index) {
print(index);
textEditController.add(TextEditingController(
text: toDoLineList[index].todoData));
textFocusNode.add(FocusNode());
textFocusNode[index].unfocus();
keyBoardFocusNode.add(FocusNode());
}
print(index);
print(textFocusNode);
return renderTextFormField(toDoLineList[index], index);
}),
),
),
],
),
);
}
}
`
i try to focusNode List move to glover and textfield wrap FocusScopeNode and many try things but i can't solve this problem...

Need to keep old values when I comeback from the second screen

I’m making a pomodoro timer app. When I go to the SettingsScreen and come back to the home screen it reset the setNum value to 0 and the done value to 0.
I need to keep those previous values when I come back from the SettingsScreen.
In the settings screen, If I changed
Navigator.of(context).push(MaterialPageRoute()
To this
Navigator.of(context).pop(MaterialPageRoute()
In home screen, it will keep the previous values and it won’t change setNum to 0 but then I cannot change Times from that screen.
How to solve this?
Home Screen
import 'dart:async';
import 'dart:ffi';
import 'package:audioplayers/audio_cache.dart';
import 'package:flutter/material.dart';
import 'package:percent_indicator/percent_indicator.dart';
import 'package:pomodoroapp/model/menu_item.dart';
import 'package:pomodoroapp/model/pomodoro_status.dart';
import 'package:pomodoroapp/screens/report_screen.dart';
import 'package:pomodoroapp/screens/settings_screen.dart';
import 'package:pomodoroapp/utils/constants.dart';
import 'package:pomodoroapp/widget/custom_button.dart';
import 'package:pomodoroapp/widget/menu_items.dart';
import 'package:pomodoroapp/widget/progress_icons.dart';
class Home extends StatefulWidget {
//////////////////////// passed (changed) values //////////////////////
final pomodoroTimeChanged;
final shortBreakTimeChanged;
final longBreakTimeChanged;
Home(
{Key key,
this.pomodoroTimeChanged,
this.shortBreakTimeChanged,
this.longBreakTimeChanged})
: super(key: key);
#override
State<Home> createState() => _HomeState(
pomodoroTimeChanged, shortBreakTimeChanged, longBreakTimeChanged);
}
//////////////////////// main button labels ////////////////////////
const _btnTextStart = 'START';
const _btnTextResumePomodoro = 'RESUME';
const _btnTextResumeBreak = 'RESUME';
const _btnTextStartShortBreak = 'START';
const _btnTextStartLongBreak = 'START';
const _btnTextStartNewSet = 'START NEW SET';
const _btnTextPause = 'PAUSE';
const _btnTextReset = 'RESET';
#override
class _HomeState extends State<Home> {
//////////////////////// values //////////////////////
int pomodoroTime;
int shortBreakTime;
int longBreakTime;
//////////////////////// default times //////////////////////
int pomodoroTimeDefault = 5;
int shortBreakTimeDefault = 2;
int longBreakTimeDefault = 3;
int pomodoriPerSet = 4;
int pomodoroTimeChanged;
int shortBreakTimeChanged;
int longBreakTimeChanged;
_HomeState(this.pomodoroTimeChanged, this.shortBreakTimeChanged,
this.longBreakTimeChanged);
static AudioCache player = AudioCache();
int remainingTime = pomodoroTotalTime;
String mainBtnText = _btnTextStart;
PomodoroStatus pomodoroStatus = PomodoroStatus.pausedPomodoro;
Timer _timer;
int pomodoroNum = 0;
int setNum = 0;
//////////////////////// dispose, to avoid memory leak //////////////////////
#override
void dispose() {
_cancelTimer();
super.dispose();
}
/////////////////////// Update state function for changed value ///////////////////////
_updateStatepomodoroTime() {
setState(() {
remainingTime = pomodoroTime;
});
}
_updateStateShortBreakTime() {
setState(() {
remainingTime = shortBreakTime;
});
}
_updateStateLongBreakTime() {
setState(() {
remainingTime = longBreakTime;
});
}
#override
void initState() {
super.initState();
player.load('bell.mp3');
//////////////////// setting an initial value //////////////////////
if (pomodoroTimeChanged != null) {
pomodoroTime = pomodoroTimeChanged;
_updateStatepomodoroTime();
} else {
pomodoroTime = pomodoroTimeDefault;
}
if (shortBreakTimeChanged != null) {
shortBreakTime = shortBreakTimeChanged;
//_updateStateShortBreakTime();
_updateStatepomodoroTime();
} else {
shortBreakTime = shortBreakTimeDefault;
}
if (longBreakTimeChanged != null) {
longBreakTime = longBreakTimeChanged;
//_updateStateLongBreakTime();
_updateStatepomodoroTime();
} else {
longBreakTime = longBreakTimeDefault;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: Colors.grey[900],
appBar: PreferredSize(
preferredSize: Size.fromHeight(27.0),
child: AppBar(
backgroundColor: Colors.transparent,
automaticallyImplyLeading: false,
iconTheme: IconThemeData(color: Colors.white, size: 10.0),
elevation: 0,
actions: [
PopupMenuButton<MenuItem>(
onSelected: (item) => onSelected(context, item),
itemBuilder: (context) =>
[...MenuItems.itemsFirst.map(buildItem).toList()],
)
],
),
),
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage(
'https://firebasestorage.googleapis.com/v0/b/flutterbricks-1926c.appspot.com/o/images%2Fwidgets%2F1634411682152%2FScreen%20Shot%202021-10-16%20at%203.14.09%20PM.png?alt=media&token=ec556af9-6dff-4020-a530-2b1eec58dafe'),
fit: BoxFit.cover,
),
),
child: Center(
child: Column(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularPercentIndicator(
radius: 220.0,
lineWidth: 15.0,
percent: _getPomodoroPercentage(),
circularStrokeCap: CircularStrokeCap.round,
center: Text(
_secondsToFormatedString(remainingTime),
style:
const TextStyle(fontSize: 40, color: Colors.white),
),
progressColor: statusColor[pomodoroStatus],
),
const SizedBox(
height: 12,
),
ProgressIcons(
total: pomodoriPerSet,
done: pomodoroNum - (setNum * pomodoriPerSet),
),
const SizedBox(
height: 12,
),
Text(
'#$setNum',
style: const TextStyle(fontSize: 15, color: Colors.grey),
),
const SizedBox(
height: 5,
),
Text(
statusDescription[pomodoroStatus],
style: const TextStyle(color: Colors.white),
),
const SizedBox(
height: 12,
),
const SizedBox(
height: 12,
),
CustomButton(
onTap: _mainButtonPressed,
text: mainBtnText,
),
CustomButton(
onTap: _resetButtonPressed,
text: _btnTextReset,
)
],
),
)
],
),
),
),
);
}
_secondsToFormatedString(int seconds) {
int roundedMinutes = seconds ~/ 60;
int remainingSeconds = seconds - (roundedMinutes * 60);
String remainingSecondsFormated;
if (remainingSeconds < 10) {
remainingSecondsFormated = '0$remainingSeconds';
} else {
remainingSecondsFormated = remainingSeconds.toString();
}
return '$roundedMinutes:$remainingSecondsFormated';
}
_getPomodoroPercentage() {
int totalTime;
switch (pomodoroStatus) {
case PomodoroStatus.runingPomodoro:
totalTime = pomodoroTime;
break;
case PomodoroStatus.pausedPomodoro:
totalTime = pomodoroTime;
break;
case PomodoroStatus.runningShortBreak:
totalTime = shortBreakTime;
break;
case PomodoroStatus.pausedShortBreak:
totalTime = shortBreakTime;
break;
case PomodoroStatus.runningLongBreak:
totalTime = longBreakTime;
break;
case PomodoroStatus.pausedLongBreak:
totalTime = longBreakTime;
break;
case PomodoroStatus.setFinished:
totalTime = pomodoroTime;
break;
}
double percentage = (totalTime - remainingTime) / totalTime;
return percentage;
}
_mainButtonPressed() {
switch (pomodoroStatus) {
case PomodoroStatus.pausedPomodoro:
_startPomodoroCountdown();
break;
case PomodoroStatus.runingPomodoro:
_pausePomodoroCountdown();
break;
case PomodoroStatus.runningShortBreak:
_pauseShortBreakCountdown();
break;
case PomodoroStatus.pausedShortBreak:
_startShortBreak();
break;
case PomodoroStatus.runningLongBreak:
_pauseLongBreakCountdown();
break;
case PomodoroStatus.pausedLongBreak:
_startLongBreak();
break;
case PomodoroStatus.setFinished:
setNum++;
_startPomodoroCountdown();
break;
}
}
_startPomodoroCountdown() {
pomodoroStatus = PomodoroStatus.runingPomodoro;
_cancelTimer();
if (_timer != null) {
_timer.cancel();
}
_timer = Timer.periodic(
Duration(seconds: 1),
(timer) => {
if (remainingTime > 0)
{
setState(() {
remainingTime--;
mainBtnText = _btnTextPause;
})
}
else
{
_playSound(),
pomodoroNum++,
_cancelTimer(),
if (pomodoroNum % pomodoriPerSet == 0)
{
pomodoroStatus = PomodoroStatus.pausedLongBreak,
setState(() {
remainingTime = longBreakTime;
mainBtnText = _btnTextStartLongBreak;
}),
}
else
{
pomodoroStatus = PomodoroStatus.pausedShortBreak,
setState(() {
remainingTime = shortBreakTime;
mainBtnText = _btnTextStartShortBreak;
}),
}
}
});
}
_startShortBreak() {
pomodoroStatus = PomodoroStatus.runningShortBreak;
setState(() {
mainBtnText = _btnTextPause;
});
_cancelTimer();
_timer = Timer.periodic(
Duration(seconds: 1),
(timer) => {
if (remainingTime > 0)
{
setState(() {
remainingTime--;
}),
}
else
{
_playSound(),
remainingTime = pomodoroTime,
_cancelTimer(),
pomodoroStatus = PomodoroStatus.pausedPomodoro,
setState(() {
mainBtnText = _btnTextStart;
}),
}
});
}
_startLongBreak() {
pomodoroStatus = PomodoroStatus.runningLongBreak;
setState(() {
mainBtnText = _btnTextPause;
});
_cancelTimer();
_timer = Timer.periodic(
Duration(seconds: 1),
(timer) => {
if (remainingTime > 0)
{
setState(() {
remainingTime--;
}),
}
else
{
_playSound(),
remainingTime = pomodoroTime,
_cancelTimer(),
pomodoroStatus = PomodoroStatus.setFinished,
setState(() {
mainBtnText = _btnTextStartNewSet;
}),
}
});
}
_pausePomodoroCountdown() {
pomodoroStatus = PomodoroStatus.pausedPomodoro;
_cancelTimer();
setState(() {
mainBtnText = _btnTextResumePomodoro;
});
}
_resetButtonPressed() {
pomodoroNum = 0;
setNum = 0;
_cancelTimer();
_stopCountdown();
}
_stopCountdown() {
pomodoroStatus = PomodoroStatus.pausedPomodoro;
setState(() {
mainBtnText = _btnTextStart;
remainingTime = pomodoroTime;
});
}
_pauseShortBreakCountdown() {
pomodoroStatus = PomodoroStatus.pausedShortBreak;
_pauseBreakCountdown();
}
_pauseLongBreakCountdown() {
pomodoroStatus = PomodoroStatus.pausedLongBreak;
_pauseBreakCountdown();
}
_pauseBreakCountdown() {
_cancelTimer();
setState(() {
mainBtnText = _btnTextResumeBreak;
});
}
_cancelTimer() {
if (_timer != null) {
_timer.cancel();
}
}
_playSound() {
player.play('bell.mp3');
}
PopupMenuItem<MenuItem> buildItem(MenuItem item) => PopupMenuItem<MenuItem>(
value: item,
child: Row(children: [
Icon(
item.icon,
color: Colors.black,
size: 20,
),
const SizedBox(
width: 12,
),
Text(item.text)
]),
);
void onSelected(BuildContext context, MenuItem item) {
switch (item) {
case MenuItems.itemSettings:
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => SettingsScreen()),
);
break;
case MenuItems.itemReport:
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ReportScreen()),
);
break;
}
}
}
class PopUpMenu extends StatelessWidget {
final List<PopupMenuEntry> menuList;
final Widget icon;
const PopUpMenu({Key key, this.menuList, this.icon}) : super(key: key);
#override
Widget build(BuildContext context) {
return PopupMenuButton(
itemBuilder: (context) => menuList,
icon: icon,
);
}
}
Settings Screen
import 'package:flutter/material.dart';
import 'package:numberpicker/numberpicker.dart';
import 'package:pomodoroapp/screens/home_screen.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({Key key}) : super(key: key);
#override
_SettingsScreen createState() => _SettingsScreen();
}
class _SettingsScreen extends State<SettingsScreen>
with TickerProviderStateMixin {
// Switch states
int _workSessionValue = 25;
int _shortBreakValue = 5;
int _longBreakValue = 15;
NumberPicker integerNumberPicker;
////////////////////// values to pass //////////////////////
int pomodoroTimeToChanged;
int shortBreakTimeToChanged;
int longBreakTimeToChanged;
// Work Session
_handleWorkValueChange(num value) {
if (value != null) {
setState(() {
_workSessionValue = value;
});
}
}
_handleWorkValueChangedExternally(num value) {
if (value != null) {
setState(() {
_workSessionValue = value;
});
integerNumberPicker.animateInt(value);
}
print('Updated pomodoro value: $_workSessionValue ');
pomodoroTimeToChanged = value * 60;
}
// Short break
_handleShortBreakValueChange(num value) {
if (value != null) {
setState(() {
_shortBreakValue = value;
});
}
}
_handleShortBreakValueChangedExternally(num value) {
if (value != null) {
setState(() {
_shortBreakValue = value;
});
integerNumberPicker.animateInt(value);
}
print('Updated short break value: $_shortBreakValue ');
shortBreakTimeToChanged = value * 60;
}
// Long Break
_handleLongBreakValueChange(num value) {
if (value != null) {
setState(() {
_longBreakValue = value;
});
}
}
_handleLongBreakChangedExternally(num value) {
if (value != null) {
setState(() {
_longBreakValue = value;
});
integerNumberPicker.animateInt(value);
}
print('Updated Long break value: $_longBreakValue ');
longBreakTimeToChanged = value * 60;
}
// Animation
AnimationController animationController;
String get timerString {
Duration duration =
animationController.duration * animationController.value;
return '${duration.inMinutes.toString().padLeft(2, '0')}\n${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this, duration: const Duration(seconds: 1500));
}
//number pick values
#override
Widget build(BuildContext context) {
integerNumberPicker = NumberPicker.integer(
initialValue: _workSessionValue,
minValue: 1,
maxValue: 50,
onChanged: _handleWorkValueChange,
);
integerNumberPicker = NumberPicker.integer(
initialValue: _shortBreakValue,
minValue: 1,
maxValue: 50,
onChanged: _handleShortBreakValueChange,
);
integerNumberPicker = NumberPicker.integer(
initialValue: _longBreakValue,
minValue: 1,
maxValue: 50,
onChanged: _handleLongBreakValueChange,
);
//UI
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.chevron_left),
onPressed: () => {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => Home(
pomodoroTimeChanged: pomodoroTimeToChanged,
shortBreakTimeChanged: shortBreakTimeToChanged,
longBreakTimeChanged: longBreakTimeToChanged,
)))
},
),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text("Timer",
textAlign: TextAlign.left,
style: TextStyle(
color: Colors.black,
fontSize: 32,
fontWeight: FontWeight.w700)),
const Divider(
thickness: 2,
color: Colors.black26,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text(
"Pomodoro",
style: TextStyle(
color: Colors.black,
fontSize: 22,
fontWeight: FontWeight.w400),
),
Row(
children: <Widget>[
Container(
width: 30,
height: 30,
child: RawMaterialButton(
shape: const CircleBorder(),
onPressed: _showWorkSessionDialog,
fillColor: Colors.amber,
elevation: 0,
child: Text(
"$_workSessionValue",
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w500),
),
),
),
const Padding(
padding: EdgeInsets.all(5),
child: Text(
"min",
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.w500),
),
)
],
)
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text(
"Short Break",
style: TextStyle(
color: Colors.black,
fontSize: 22,
fontWeight: FontWeight.w400),
),
Row(
children: <Widget>[
Container(
width: 30,
height: 30,
child: RawMaterialButton(
shape: CircleBorder(),
onPressed: _showShortBreakDialog,
fillColor: Colors.amber,
elevation: 0,
child: Text(
"$_shortBreakValue",
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w500),
),
),
),
const Padding(
padding: EdgeInsets.all(5),
child: Text(
"min",
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.w500),
),
)
],
)
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const Text(
"Long Break",
style: TextStyle(
color: Colors.black,
fontSize: 22,
fontWeight: FontWeight.w400),
),
Row(
children: <Widget>[
Container(
width: 30,
height: 30,
child: RawMaterialButton(
shape: CircleBorder(),
onPressed: _showLongBreakDialog,
fillColor: Colors.amber,
elevation: 0,
child: Text(
"$_longBreakValue",
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w500),
),
),
),
const Padding(
padding: EdgeInsets.all(5),
child: Text(
"min",
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.w500),
),
)
],
)
],
),
)
],
)
],
),
),
);
}
//dialog boxes
_showWorkSessionDialog() {
showDialog<int>(
context: context,
builder: (BuildContext context) {
return NumberPickerDialog.integer(
minValue: 0,
maxValue: 50,
initialIntegerValue: _workSessionValue,
title: const Text("Select a minute"),
);
},
).then(_handleWorkValueChangedExternally);
}
_showShortBreakDialog() {
showDialog<int>(
context: context,
builder: (BuildContext context) {
return NumberPickerDialog.integer(
minValue: 0,
maxValue: 50,
initialIntegerValue: _shortBreakValue,
title: const Text("Select a minute"),
);
},
).then(_handleShortBreakValueChangedExternally);
}
_showLongBreakDialog() {
showDialog<int>(
context: context,
builder: (BuildContext context) {
return NumberPickerDialog.integer(
minValue: 0,
maxValue: 50,
initialIntegerValue: _longBreakValue,
title: const Text("Select a minute"),
);
},
).then(_handleLongBreakChangedExternally);
}
}

Video player play image and video looping flutter

can video player play image for 10sec then loop to next video ?
How can i loop image only for 10sec then loop into next new video in my video clip data or video player controller ?
I manage to add video to video player but don't know how to add image to the player.
video clip data
class VideoClip {
final String fileName;
final String thumbName;
final String title;
final String parent;
int runningTime;
VideoClip(this.title, this.fileName, this.thumbName, this.runningTime, this.parent);
String videoPath() {
return "$parent/$fileName";
}
String thumbPath() {
return "$parent/$thumbName";
}
static List<VideoClip> remoteClips = [
VideoClip("For Bigger Fun", "ForBiggerFun.mp4", "images/ForBiggerFun.jpg", 0, "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample"),
VideoClip("Elephant Dream", "ElephantsDream.mp4", "images/ForBiggerBlazes.jpg", 0, "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample"),
VideoClip("BigBuckBunny", "BigBuckBunny.mp4", "images/BigBuckBunny.jpg", 0, "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample"),
];
}
Video player controller
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_video_list_sample/clips.dart';
import 'package:video_player/video_player.dart';
import 'package:wakelock/wakelock.dart';
class PlayPage extends StatefulWidget {
PlayPage({Key key, #required this.clips}) : super(key: key);
final List<VideoClip> clips;
#override
_PlayPageState createState() => _PlayPageState();
}
class _PlayPageState extends State<PlayPage> {
VideoPlayerController _controller;
List<VideoClip> get _clips {
return widget.clips;
}
var _playingIndex = -1;
var _disposed = false;
var _isFullScreen = false;
var _isEndOfClip = false;
var _progress = 0.0;
var _showingDialog = false;
Timer _timerVisibleControl;
double _controlAlpha = 1.0;
var _playing = false;
bool get _isPlaying {
return _playing;
}
set _isPlaying(bool value) {
_playing = value;
_timerVisibleControl?.cancel();
if (value) {
_timerVisibleControl = Timer(Duration(seconds: 2), () {
if (_disposed) return;
setState(() {
_controlAlpha = 0.0;
});
});
} else {
_timerVisibleControl = Timer(Duration(milliseconds: 200), () {
if (_disposed) return;
setState(() {
_controlAlpha = 1.0;
});
});
}
}
void _onTapVideo() {
debugPrint("_onTapVideo $_controlAlpha");
setState(() {
_controlAlpha = _controlAlpha > 0 ? 0 : 1;
});
_timerVisibleControl?.cancel();
_timerVisibleControl = Timer(Duration(seconds: 2), () {
if (_isPlaying) {
setState(() {
_controlAlpha = 0.0;
});
}
});
}
#override
void initState() {
Wakelock.enable();
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
_initializeAndPlay(0);
super.initState();
}
#override
void dispose() {
_disposed = true;
_timerVisibleControl?.cancel();
Wakelock.disable();
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
_exitFullScreen();
_controller?.pause(); // mute instantly
_controller?.dispose();
_controller = null;
super.dispose();
}
void _toggleFullscreen() async {
if (_isFullScreen) {
_exitFullScreen();
} else {
_enterFullScreen();
}
}
void _enterFullScreen() async {
debugPrint("enterFullScreen");
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
await SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
if (_disposed) return;
setState(() {
_isFullScreen = true;
});
}
void _exitFullScreen() async {
debugPrint("exitFullScreen");
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
if (_disposed) return;
setState(() {
_isFullScreen = false;
});
}
void _initializeAndPlay(int index) async {
print("_initializeAndPlay ---------> $index");
final clip = _clips[index];
final controller = clip.parent.startsWith("http")
? VideoPlayerController.network(clip.videoPath())
: VideoPlayerController.asset(clip.videoPath());
final old = _controller;
_controller = controller;
if (old != null) {
old.removeListener(_onControllerUpdated);
old.pause();
debugPrint("---- old contoller paused.");
}
debugPrint("---- controller changed.");
setState(() {});
controller
..initialize().then((_) {
debugPrint("---- controller initialized");
old?.dispose();
_playingIndex = index;
_duration = null;
_position = null;
controller.addListener(_onControllerUpdated);
controller.play();
setState(() {});
});
}
var _updateProgressInterval = 0.0;
Duration _duration;
Duration _position;
void _onControllerUpdated() async {
if (_disposed) return;
// blocking too many updation
// important !!
final now = DateTime.now().millisecondsSinceEpoch;
if (_updateProgressInterval > now) {
return;
}
_updateProgressInterval = now + 500.0;
final controller = _controller;
if (controller == null) return;
if (!controller.value.isInitialized) return;
if (_duration == null) {
_duration = _controller.value.duration;
}
var duration = _duration;
if (duration == null) return;
var position = await controller.position;
_position = position;
final playing = controller.value.isPlaying;
final isEndOfClip = position.inMilliseconds > 0 && position.inSeconds + 1 >= duration.inSeconds;
if (playing) {
// handle progress indicator
if (_disposed) return;
setState(() {
_progress = position.inMilliseconds.ceilToDouble() / duration.inMilliseconds.ceilToDouble();
});
}
// handle clip end
if (_isPlaying != playing || _isEndOfClip != isEndOfClip) {
_isPlaying = playing;
_isEndOfClip = isEndOfClip;
debugPrint("updated -----> isPlaying=$playing / isEndOfClip=$isEndOfClip");
if (isEndOfClip && !playing) {
debugPrint("========================== End of Clip / Handle NEXT ========================== ");
final isComplete = _playingIndex == _clips.length - 1;
if (isComplete) {
print("played all!!");
if (!_showingDialog) {
_showingDialog = true;
_showPlayedAllDialog().then((value) {
_exitFullScreen();
_showingDialog = false;
});
}
} else {
_initializeAndPlay(_playingIndex + 1);
}
}
}
}
Future<bool> _showPlayedAllDialog() async {
return showDialog<bool>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(child: Text("Played all videos.")),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text("Close"),
),
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _isFullScreen
? null
: AppBar(
title: Text("Play View"),
),
body: _isFullScreen
? Container(
child: Center(child: _playView(context)),
decoration: BoxDecoration(color: Colors.black),
)
: Column(children: <Widget>[
Container(
child: Center(child: _playView(context)),
decoration: BoxDecoration(color: Colors.black),
),
Expanded(
child: _listView(),
),
]),
);
}
void _onTapCard(int index) {
_initializeAndPlay(index);
}
Widget _playView(BuildContext context) {
final controller = _controller;
if (controller != null && controller.value.isInitialized) {
return AspectRatio(
//aspectRatio: controller.value.aspectRatio,
aspectRatio: 16.0 / 9.0,
child: Stack(
children: <Widget>[
GestureDetector(
child: VideoPlayer(controller),
onTap: _onTapVideo,
),
_controlAlpha > 0
? AnimatedOpacity(
opacity: _controlAlpha,
duration: Duration(milliseconds: 250),
child: _controlView(context),
)
: Container(),
],
),
);
} else {
return AspectRatio(
aspectRatio: 16.0 / 9.0,
child: Center(
child: Text(
"Preparing ...",
style: TextStyle(color: Colors.white70, fontWeight: FontWeight.bold, fontSize: 18.0),
)),
);
}
}
Widget _listView() {
return ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
itemCount: _clips.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
borderRadius: BorderRadius.all(Radius.circular(6)),
splashColor: Colors.blue[100],
onTap: () {
_onTapCard(index);
},
child: _buildCard(index),
);
},
).build(context);
}
Widget _controlView(BuildContext context) {
return Column(
children: <Widget>[
_topUI(),
Expanded(
child: _centerUI(),
),
_bottomUI()
],
);
}
Widget _centerUI() {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: () async {
final index = _playingIndex - 1;
if (index > 0 && _clips.length > 0) {
_initializeAndPlay(index);
}
},
child: Icon(
Icons.fast_rewind,
size: 36.0,
color: Colors.white,
),
),
TextButton(
onPressed: () async {
if (_isPlaying) {
_controller?.pause();
_isPlaying = false;
} else {
final controller = _controller;
if (controller != null) {
final pos = _position?.inSeconds ?? 0;
final dur = _duration?.inSeconds ?? 0;
final isEnd = pos == dur;
if (isEnd) {
_initializeAndPlay(_playingIndex);
} else {
controller.play();
}
}
}
setState(() {});
},
child: Icon(
_isPlaying ? Icons.pause : Icons.play_arrow,
size: 56.0,
color: Colors.white,
),
),
TextButton(
onPressed: () async {
final index = _playingIndex + 1;
if (index < _clips.length - 1) {
_initializeAndPlay(index);
}
},
child: Icon(
Icons.fast_forward,
size: 36.0,
color: Colors.white,
),
),
],
));
}
String convertTwo(int value) {
return value < 10 ? "0$value" : "$value";
}
Widget _topUI() {
final noMute = (_controller?.value?.volume ?? 0) > 0;
final duration = _duration?.inSeconds ?? 0;
final head = _position?.inSeconds ?? 0;
final remained = max(0, duration - head);
final min = convertTwo(remained ~/ 60.0);
final sec = convertTwo(remained % 60);
return Row(
children: <Widget>[
InkWell(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [
BoxShadow(offset: const Offset(0.0, 0.0), blurRadius: 4.0, color: Color.fromARGB(50, 0, 0, 0)),
]),
child: Icon(
noMute ? Icons.volume_up : Icons.volume_off,
color: Colors.white,
)),
),
onTap: () {
if (noMute) {
_controller?.setVolume(0);
} else {
_controller?.setVolume(1.0);
}
setState(() {});
},
),
Expanded(
child: Container(),
),
Text(
"$min:$sec",
style: TextStyle(
color: Colors.white,
shadows: <Shadow>[
Shadow(
offset: Offset(0.0, 1.0),
blurRadius: 4.0,
color: Color.fromARGB(150, 0, 0, 0),
),
],
),
),
SizedBox(width: 10)
],
);
}
Widget _bottomUI() {
return Row(
children: <Widget>[
SizedBox(width: 20),
Expanded(
child: Slider(
value: max(0, min(_progress * 100, 100)),
min: 0,
max: 100,
onChanged: (value) {
setState(() {
_progress = value * 0.01;
});
},
onChangeStart: (value) {
debugPrint("-- onChangeStart $value");
_controller?.pause();
},
onChangeEnd: (value) {
debugPrint("-- onChangeEnd $value");
final duration = _controller?.value?.duration;
if (duration != null) {
var newValue = max(0, min(value, 99)) * 0.01;
var millis = (duration.inMilliseconds * newValue).toInt();
_controller?.seekTo(Duration(milliseconds: millis));
_controller?.play();
}
},
),
),
IconButton(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
color: Colors.yellow,
icon: Icon(
Icons.fullscreen,
color: Colors.white,
),
onPressed: _toggleFullscreen,
),
],
);
}
Widget _buildCard(int index) {
final clip = _clips[index];
final playing = index == _playingIndex;
String runtime;
if (clip.runningTime > 60) {
runtime = "${clip.runningTime ~/ 60}분 ${clip.runningTime % 60}초";
} else {
runtime = "${clip.runningTime % 60}초";
}
return Card(
child: Container(
padding: EdgeInsets.all(4),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 8),
child: clip.parent.startsWith("http")
? Image.network(clip.thumbPath(), width: 70, height: 50, fit: BoxFit.fill)
: Image.asset(clip.thumbPath(), width: 70, height: 50, fit: BoxFit.fill),
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(clip.title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Padding(
child: Text("$runtime", style: TextStyle(color: Colors.grey[500])),
padding: EdgeInsets.only(top: 3),
)
]),
),
Padding(
padding: EdgeInsets.all(8.0),
child: playing
? Icon(Icons.play_arrow)
: Icon(
Icons.play_arrow,
color: Colors.grey.shade300,
),
),
],
),
),
);
}
}

How to repeatedly apply animation to a Stateless Widget in Flutter when the widget's content is updated based on new input?

I am trying to apply some animation on a set of grid items in my app. For this purpose I am using the package: https://pub.dev/packages/flutter_staggered_animations
When the screen loads, the grid items appear in a fade in manner as set in the AnimationConfiguration.staggeredGrid()
However, when I update the filters or sort criterion, by clicking on relevant radio buttons, checkboxes or moving sliders, the updated grid items appear without any animation. I want the updated grid items to appear with the same type of animation as they do when the page initially loads.
I understand that the grid items are displayed in a stateless widget. How do I get to animate the updated grid items? Is there a way to convert the buildResult() to a stateful widget? If so, how to do it?
Full Code:
import 'package:flutter/material.dart';
import 'package:switch_circle_color/model/girls.dart';
import 'package:switch_circle_color/screens/gender.dart';
import 'package:switch_circle_color/screens/girls_cart.dart';
import 'package:switch_circle_color/screens/selected_girl_details.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import '../global_variables/global_variables.dart';
class GirlsScreen extends StatefulWidget {
GirlsScreen({Key? key}) : super(key: key);
#override
State<GirlsScreen> createState() => _GirlsScreenState();
}
class _GirlsScreenState extends State<GirlsScreen> {
int distance = 15;
int age = 25;
String sortCriterion = "distance";
bool isSingle = false;
bool isSerious = false;
void changeDistance(double num) {
setState(() {
distance = num.round();
populateFilteredGirls(distance, age);
//buildResult(context);
});
}
void changeAgeLimit(double a) {
setState(() {
age = a.round();
populateFilteredGirls(distance, age);
//buildResult(context);
});
}
List<Girls> allGirls = [
Girls("Reshmita", 25, 33, "Married", "Serious"),
Girls("Ankita", 17, 26, "Single", "Serious"),
Girls("Rupali", 42, 28, "Single", "Casual"),
Girls("Monica", 50, 24, "Single", "Casual"),
Girls("Sakshi", 9, 27, "Married", "Casual"),
];
List<Girls> filteredGirlsbyDistance = [];
List<Girls> filteredGirlsbyAge = [];
List<Girls> filteredGirls = [];
List<Girls> filteredGirlsbySerious = [];
List<Girls> filteredGirlsbySingle = [];
//List<Girls> girlsCart = [];
void addGirlToCart(Girls girl) {
girlsCart.add(girl);
cartValue = cartValue + girl.age;
}
void removeGirlsfromCart(Girls girl) {
girlsCart.remove(girl);
cartValue = cartValue - girl.age;
}
String selectedGirlName = "";
int? selectedGirlDistance;
#override
void initState() {
super.initState();
//filteredGirls = allGirls;
distance = 20;
age = 28;
sortCriterion = "distance";
isSingle = false;
isSerious = false;
(sortCriterion == "distance")
? allGirls.sort((a, b) => a.distance.compareTo(b.distance))
: allGirls.sort((a, b) => a.age.compareTo(b.age));
populateFilteredGirls(distance, age);
}
void populateFilteredGirls(int dis, int ag) {
filteredGirlsbyDistance.clear();
filteredGirlsbyAge.clear();
filteredGirlsbySingle.clear();
filteredGirlsbySerious.clear();
filteredGirls.clear();
//int len = filteredGirls.length;
for (int i = 0; i < allGirls.length; i++) {
if (allGirls[i].distance <= dis) {
filteredGirlsbyDistance.add(allGirls[i]);
}
}
filteredGirls = filteredGirlsbyDistance;
for (int i = 0; i < filteredGirls.length; i++) {
if (filteredGirls[i].age <= ag) {
filteredGirlsbyAge.add(filteredGirls[i]);
}
}
filteredGirls = filteredGirlsbyAge;
//len = filteredGirls.length;
if (isSingle == true) {
for (int i = 0; i < filteredGirls.length; i++) {
if (filteredGirls[i].status.toLowerCase() == "single") {
filteredGirlsbySingle.add(filteredGirls[i]);
}
}
filteredGirls = filteredGirlsbySingle;
}
if (isSerious == true) {
for (int i = 0; i < filteredGirls.length; i++) {
if (filteredGirls[i].lookingFor.toLowerCase() == "serious") {
filteredGirlsbySerious.add(filteredGirls[i]);
}
}
filteredGirls = filteredGirlsbySerious;
}
//filteredGirls = filteredGirls.toSet().toList();
}
Widget buildResult(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: AnimationLimiter(
child: GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
childAspectRatio: 2,
children: List.generate(filteredGirls.length, (index) {
return AnimationConfiguration.staggeredGrid(
columnCount: 2,
position: index,
duration: const Duration(milliseconds: 375),
child: ScaleAnimation(
child: FadeInAnimation(
child: ListTile(
leading: InkWell(
child: const Icon(Icons.girl_outlined),
onTap: () {
removeGirlsfromCart(filteredGirls[index]);
},
),
trailing: InkWell(
child: Text("${filteredGirls[index].distance} km away"),
onTap: () {
addGirlToCart(filteredGirls[index]);
},
),
title: Text("${filteredGirls[index].name}"),
subtitle: Text(
"${filteredGirls[index].age} years old, ${filteredGirls[index].status}, ${filteredGirls[index].lookingFor}"),
onTap: () {
// setState(() {
// selectedGirlName = filteredGirls[index].name;
// selectedGirlDistance = filteredGirls[index].distance;
// });
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SelectedGirlDetails(
girl: filteredGirls[index],
)));
},
),
),
),
);
}),
//itemCount: ,
//itemBuilder: (BuildContext context, int index) {
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Padding(padding: EdgeInsets.all(20)),
// ElevatedButton(
// onPressed: () {
// Navigator.push(context,
// MaterialPageRoute(builder: (context) => GenderScreen()));
// },
// child: Text("Go to Gender Screen")),
// Padding(padding: EdgeInsets.all(30)),
Text("Set max distance"),
Padding(padding: EdgeInsets.all(10)),
Slider(
min: 1.0,
max: 100.0,
divisions: 100,
activeColor: Colors.green,
inactiveColor: Colors.orange,
label: 'Set distance value',
value: distance.toDouble(),
onChanged: (value) {
changeDistance(value);
},
),
Padding(padding: EdgeInsets.all(10)),
Text("Current distance is $distance kms"),
Padding(padding: EdgeInsets.all(10)),
Text("Set max age"),
Padding(padding: EdgeInsets.all(10)),
Slider(
min: 18.0,
max: 60.0,
divisions: 42,
activeColor: Colors.green,
inactiveColor: Colors.orange,
label: 'Set age limit',
value: age.toDouble(),
onChanged: (value) {
changeAgeLimit(value);
},
),
Padding(padding: EdgeInsets.all(10)),
Text("Age limit is $age years"),
Padding(padding: EdgeInsets.all(10)),
Text("Sort by:"),
Padding(padding: EdgeInsets.all(7.5)),
ListTile(
//minLeadingWidth: 30,
title: Text("Age"),
leading: Radio(
value: "age",
groupValue: sortCriterion,
onChanged: (value) {
setState(() {
sortCriterion = value.toString();
allGirls.sort((a, b) => a.age.compareTo(b.age));
populateFilteredGirls(distance, age);
//buildResult(context);
});
},
),
),
Padding(padding: EdgeInsets.all(7.5)),
ListTile(
//minLeadingWidth: 30,
title: Text("Distance"),
leading: Radio(
value: "distance",
groupValue: sortCriterion,
onChanged: (value) {
setState(() {
sortCriterion = value.toString();
allGirls.sort((a, b) => a.distance.compareTo(b.distance));
populateFilteredGirls(distance, age);
//buildResult(context);
});
},
),
),
Padding(padding: EdgeInsets.all(10)),
Text("Is Single?"),
Padding(padding: EdgeInsets.all(2.5)),
InkWell(
onTap: () {
setState(() {
isSingle = !isSingle;
populateFilteredGirls(distance, age);
//buildResult(context);
});
},
child: (isSingle == false)
? Icon(Icons.check_box_outline_blank)
: Icon(Icons.check_box),
),
Padding(padding: EdgeInsets.all(5)),
Text("Is Serious?"),
Padding(padding: EdgeInsets.all(2.5)),
InkWell(
onTap: () {
setState(() {
isSerious = !isSerious;
populateFilteredGirls(distance, age);
//buildResult(context);
});
},
child: (isSerious == false)
? Icon(Icons.check_box_outline_blank)
: Icon(Icons.check_box),
),
Padding(padding: EdgeInsets.all(10)),
buildResult(context),
Padding(padding: EdgeInsets.all(25)),
ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => GirlsCart()));
},
child: Text("Go to Girls Cart")),
Padding(padding: EdgeInsets.all(25)),
],
),
));
}
}
Video for reference:
Cant see any controller for this. You can rebuild the full widget by changing the widget key. But try to find better way.
Create a variable that will change used as key value. While you like to animate, change the assign new value on key variable. I am using int count = 0;
On state class
int count = 0;
And use onkey
body: AnimationLimiter(
key: ValueKey("list $count"),
When ever like to animate increase the counter.
onPressed: () {
count++;
setState(() {});
},

Getting Unexptected null value with Responsive datatable Flutter

I have implemented this widget 'Responsive datatable' to my Flutter web app.
But I am having some issues and getting this error:
The following TypeErrorImpl was thrown building ResponsiveDatatable(dirty, dependencies: [MediaQuery], state: _ResponsiveDatatableState#5acd2):Unexpected null value.
It works and I am getting data inside my Datatable when I scale my browser to tablet size, but when I click on fullscreen the error shows.
Here is the my code:
class UsersPage extends StatefulWidget {
static const String id = "users_page";
UsersPage({Key? key}) : super(key: key);
#override
_UsersPageState createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
late List<DatatableHeader> _headers;
List<int> _perPages = [5, 10, 15, 100];
int _total = 100;
int? _currentPerPage = 5;
int _currentPage = 1;
bool _isSearch = false;
List<Map<String, dynamic>> _source = [];
List<Map<String, dynamic>> _selecteds = [];
String _selectableKey = "id";
String? _sortColumn;
bool _sortAscending = true;
bool _isLoading = true;
bool _showSelect = true;
UserServices _userServices = UserServices();
List<UserModel> _users = <UserModel>[];
List<UserModel> get users => _users;
Future _loadFromFirebase() async {
_users = await _userServices.getAllUsers();
}
List<Map<String, dynamic>> _getUsersData() {
_isLoading = true;
List<Map<String, dynamic>> temps = [];
var i = _users.length;
print(i);
// ignore: unused_local_variable
for (UserModel userData in _users) {
print(userData.name);
print(userData.email);
temps.add({
"id": userData.uid,
"email": userData.email,
"name": userData.name,
});
i++;
}
return temps;
}
_initializeData() async {
_isLoading = true;
await _loadFromFirebase();
_source.addAll(_getUsersData());
print(_source);
}
onSort(dynamic value) {
_sortColumn = value;
_sortAscending = !_sortAscending;
if (_sortAscending) {
_source
.sort((a, b) => b["$_sortColumn"].compareTo(a["$_sortColumn"]));
} else {
_source
.sort((a, b) => a["$_sortColumn"].compareTo(b["$_sortColumn"]));
}
}
onChanged(int value) {
_currentPerPage = value;
}
previous() {
_currentPage = _currentPage >= 2 ? _currentPage - 1 : 1;
}
next() {
_currentPage++;
}
#override
void initState() {
super.initState();
/// set headers
_headers = [
DatatableHeader(
text: "ID",
value: "id",
show: true,
sortable: true,
textAlign: TextAlign.center),
DatatableHeader(
text: "Name",
value: "name",
show: true,
flex: 2,
sortable: true,
editable: true,
textAlign: TextAlign.left),
DatatableHeader(
text: "SKU",
value: "email",
show: true,
sortable: true,
textAlign: TextAlign.center),
];
_initializeData();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(0),
constraints: BoxConstraints(
maxHeight: 700,
),
child: Card(
elevation: 1,
shadowColor: Colors.black,
clipBehavior: Clip.none,
child: ResponsiveDatatable(
title: !_isSearch
? ElevatedButton.icon(
onPressed: () {},
icon: Icon(Icons.add),
label: Text("ADD CATEGORY")) : null,
actions: [
if (_isSearch)
Expanded(
child: TextField(
decoration: InputDecoration(
prefixIcon: IconButton(
icon: Icon(Icons.cancel),
onPressed: () {
setState(() {
_isSearch = false;
});
}),
suffixIcon: IconButton(
icon: Icon(Icons.search), onPressed: () {})),
)),
if (!_isSearch)
IconButton(
icon: Icon(Icons.search),
onPressed: () {
setState(() {
_isSearch = true;
});
})
],
headers: _headers,
source: _source,
selecteds: _selecteds,
showSelect: _showSelect,
autoHeight: true,
onTabRow: (data) {
print(data);
},
onSort: onSort,
sortAscending: _sortAscending,
sortColumn: _sortColumn,
isLoading: _isLoading,
onSelect: (value, item) {
print("$value $item ");
if (value!) {
setState(() => _selecteds.add(item));
} else {
setState(
() => _selecteds.removeAt(_selecteds.indexOf(item)));
}
},
onSelectAll: (value) {
if (value!) {
setState(() => _selecteds =
_source.map((entry) => entry).toList().cast());
} else {
setState(() => _selecteds.clear());
}
},
footers: [
Container(
padding: EdgeInsets.symmetric(horizontal: 15),
child: Text("Rows per page:"),
),
if (_perPages != null)
Container(
padding: EdgeInsets.symmetric(horizontal: 15),
child: DropdownButton(
value: _currentPerPage,
items: _perPages
.map((e) => DropdownMenuItem(
child: Text("$e"),
value: e,
))
.toList(),
onChanged: (value) {}),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 15),
child: Text(
"${_currentPage} - ${_currentPage} of ${_total}"),
),
IconButton(
icon: Icon(
Icons.arrow_back_ios,
size: 16,
),
onPressed: previous,
padding: EdgeInsets.symmetric(horizontal: 15),
),
IconButton(
icon: Icon(Icons.arrow_forward_ios, size: 16),
onPressed: next,
padding: EdgeInsets.symmetric(horizontal: 15),
)
],
),
),
),
],
),
);
}
}