Reputation: 443
I'm using a CupertinoModalPopup with a CupertinoActionSheet widget to display a set of options for the user to choose from.
I intend to show a circular loading indicator followed by a checkmark(tick) to give the user the impression that he has clicked the option and it is saved after popping out using Navingator.pop(context). First , I'm attempting to do this with a delay of 100milliseconds using Future.delayed after clicking onPressed using only a Widget that displays a circular loading indicator for now. But it isn't working.
Here is my code to better explain what I'm doing. Only relevant code is published.
@override
Widget build(BuildContext context) {
return CupertinoActionSheet(
title: KNText(
style: TextStyle(color: primaryTextColor),
'Video sound settings',
),
message: KNText(
style: TextStyle(color: primaryTextColor),
'Please choose from an option below :',
),
actions: <Widget>[
Container(
color: primaryColor,
child: CupertinoActionSheetAction(
onPressed: ({int index = 0}) async {
await Future.delayed(const Duration(milliseconds: 100), () {
setState(() {
VideosSoundSetting newType =
allVideosSoundSettings[index];
widget.onTypeChanged(newType);
_setEnabledInProgress(true);
Navigator.pop(context);
});
setState(() {
_enabledInProgress = false;
});
});
},
A circular loading indicator(delay : 100 milliseconds) followed by a checkmark tick(delay:50 milliseconds) will appear for a brief time after clicking on either option to give the user an impression that its a success. Currently , only circular loading indicator is configured below.
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 153.0),
child: KNText(
style: TextStyle(color: primaryTextColor),
'Enabled',
),
),
_enabledInProgress
? const CircularProgressIndicator()
: Container()
],
),
)),
Container(
color: primaryColor,
child: CupertinoActionSheetAction(
// A circular loading indicator(delay : 100 milliseconds) followed by a checkmark tick(delay:50 milliseconds) will appear for a brief time after clicking on either option to give the user an impression that its a success. Currently , only circular loading indicator is configured below.
onPressed: ({int index = 0}) async {
await Future.delayed(const Duration(milliseconds: 100), () {
setState(() {
VideosSoundSetting newType =
allVideosSoundSettings[index];
widget.onTypeChanged(newType);
_setEnabledInProgress(true);
Navigator.pop(context);
});
setState(() {
_enabledInProgress = false;
});
});
},
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 150.0),
child: KNText(
style: TextStyle(color: primaryTextColor),
'Disabled',
),
),
_enabledInProgress
? const CircularProgressIndicator()
: Container()
],
),
)),
],
cancelButton: Container(
color: primaryColor,
child: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () {
Navigator.pop(context);
},
child: const Row(children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 155.0),
child: KNText(
style: TextStyle(
color: Color.fromRGBO(255, 0, 0, 1.0),
fontWeight: FontWeight.w700),
'Cancel',
),
),
]),
),
));
}
void _setEnabledInProgress(bool enabledInProgress) {
_enabledInProgress = enabledInProgress;
}
What is working :
The options and selection does work with a delay in milliseconds as specified
What doesn't :
The circular progress indicator at the very end of 'Enabled' and Disabled actions doesn't appear. After fixing the circular progress indicator with a delayed duration, I intend to call a checkmark tick widget after which the checkmark should appear and popout from the widget itself,.
If I could be pointed in the right direction of how best to achieve the above goal of
EDIT as on 29-04-2024
Based on the guidance received from @anqit in the comments below, I did manage to somewhat make some progress. have updated the code to reflect the changes.
Upvotes: 5
Views: 252
Reputation: 354
Summary:
The StatefulBuilder
widgets may not be required, as the BuildContext
may come from your custom widget. The ElevatedButton
serves as an example of launching the CupertinoActionSheet
, which can be substituted with whatever method you are using. Note that I didn't make use of custom widgets such as KNText
. The two parts to your question to my understanding are how to show loading, and how to show a checkmark after selection and loading. Note I set the delay for loading to 1000ms, so you can see the loading longer in the example.
Loading Summary
The loading CircularProgressIndicator
is shown by roughly following the below steps. ref _showLoading
function in the code below.
showDialog
showDialog
return a CircularProgressIndicator
Future.delayed
(in production a network call?)Navigator.pop
to close the loading dialogCheckmark Summary:
The checkmark can be shown in many ways, in this case I made use of Visibility
to hide and show the checkmarks based on the values in the Map
named selected
in the code below. The values in selected
are updated when the buttons in the modal are pressed (after loading is done). The rough process is as follows
selected
based on which button was pressedsetState
was called. When building the checkmarks are shown based on the values in selected
.
Code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
// used to indicate which option is selected
Map<String, bool> selected = {
"enabled": true,
"disabled": false,
};
// shows the loading indicator
Future<void> _showLoading({
required BuildContext context,
}) async {
// show dialog overlay with just a progress indicator
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => const Center(
child: CircularProgressIndicator(
color: Colors.red,
),
),
);
// run delay - assume swapping for some kind of network call
// on delay done pop loading dialog
await Future.delayed(
const Duration(
milliseconds: 1000,
),
() {
Navigator.pop(
context,
);
},
);
}
runApp(
MaterialApp(
home: Scaffold(
body: StatefulBuilder(
builder: (context, setState) => Center(
// button to trigger modal
child: ElevatedButton(
child: const Text(
"Click to test",
),
onPressed: () => showCupertinoModalPopup(
context: context,
builder: (BuildContext modalContext) {
/*
Rough example begins
*/
return StatefulBuilder(
builder: (context, setState) => CupertinoActionSheet(
title: const Text(
"Video sound settings",
),
message: const Text(
"Please choose from an option below :",
),
actions: [
CupertinoActionSheetAction(
onPressed: ({
int index = 0,
}) async {
// show loading, and wait till function done
await _showLoading(
context: context,
);
// tell flutter to build, check will be shown based on values set on next build
setState(
() {
selected["enabled"] = true;
selected["disabled"] = false;
},
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Enabled",
),
Visibility(
visible: selected["enabled"]!,
child: const Icon(
Icons.check,
),
),
],
),
),
CupertinoActionSheetAction(
onPressed: ({
int index = 0,
}) async {
await _showLoading(
context: context,
);
// opposite values
setState(
() {
selected["enabled"] = false;
selected["disabled"] = true;
},
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Disabled",
),
Visibility(
visible: selected["disabled"]!,
child: const Icon(
Icons.check,
),
),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () {
Navigator.pop(
context,
);
},
child: const Text(
"Cancel",
),
),
),
);
},
),
),
),
),
),
),
);
}
Updated code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
// used to indicate which option is selected
Map<String, bool> selected = {
"enabled": true,
"disabled": false,
};
// if true show loading
bool loadingFlag = false;
runApp(
MaterialApp(
home: Scaffold(
body: StatefulBuilder(
builder: (context, setState) => Center(
// button to trigger modal
child: ElevatedButton(
child: const Text(
"Click to test",
),
onPressed: () => showCupertinoModalPopup(
context: context,
builder: (BuildContext modalContext) {
/*
Rough example begins
*/
return StatefulBuilder(
builder: (context, setState) => CupertinoActionSheet(
title: const Text(
"Video sound settings",
),
message: const Text(
"Please choose from an option below :",
),
actions: [
CupertinoActionSheetAction(
onPressed: ({
int index = 0,
}) async {
// update flags and set loading flag, then build
setState(
() {
selected["enabled"] = true;
selected["disabled"] = false;
loadingFlag = true;
},
);
// wait for delay, change loading flag, then build
await Future.delayed(
const Duration(
milliseconds: 1000,
),
() {
setState(
() {
loadingFlag = false;
},
);
},
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Enabled",
),
Visibility(
visible: selected["enabled"]!,
// check loading flag to determine if should show loading or check
child: loadingFlag
? const CircularProgressIndicator()
: const Icon(
Icons.check,
),
),
],
),
),
CupertinoActionSheetAction(
onPressed: ({
int index = 0,
}) async {
// opposite enable disable
setState(
() {
selected["enabled"] = false;
selected["disabled"] = true;
loadingFlag = true;
},
);
await Future.delayed(
const Duration(
milliseconds: 1000,
),
() {
setState(
() {
loadingFlag = false;
},
);
},
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Disabled",
),
Visibility(
visible: selected["disabled"]!,
child: loadingFlag
? const CircularProgressIndicator()
: const Icon(
Icons.check,
),
),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () {
Navigator.pop(
context,
);
},
child: const Text(
"Cancel",
),
),
),
);
},
),
),
),
),
),
),
);
}
Upvotes: 3