How to test this GestureDetector in Flutter with Patrol? - flutter

In my app, the widget tree is DeleteItem GestureDetector -> InvoiceTotal Widget -> ItemListWidget -> ListView -> EditInvoiceClass
I tried to use PatrolTest
await $(#InvoiceTotal).$(#DeleteItem).scrollTo().tap();
$.pumpAndSettle;
to test the DeleteItem GestureDetector but I got an Exception --
WaitUntilVisibleTimeoutException (TimeoutException after 0:00:10.000000: Finder exactly one widget with key \[\<'DeleteItem'\>\] that has ancestor(s) with key \[\<'InvoiceTotal'\>\] (ignoring all but first) (ignoring offstage widgets): GestureDetector-\[\<'DeleteItem'\>\](startBehavior: start, dependencies: \[MediaQuery, \_ScrollableScope\]) did not find any visible widgets)
I want to know why the DeleteItem GestureDetector is invisible? How can I fix the problem?
My Flutter code attached below.
// Code Segment of EditInvoice Class
return Consumer<ItemContents>(
builder: (context, value, child) {
return Consumer<DarkThemeProvider>(
builder: (context, value1, child1) {
return Expanded(
child: Column(
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: myTheme.normalPadding),
child: const goBack(),
),
Expanded(
child: Scrollbar(
child: ListView(
padding: EdgeInsets.zero,
children: [
Padding(
padding: EdgeInsets.all(myTheme.normalPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
ItemListWidget(
key: const Key('ItemListWidget'),
context: context,
myTextTheme: myTextTheme,
myTextColor: myTextColor,
value: value,
myTheme: myTheme,
invoicesContents: vm.invoicesData(
vm,
newAddModel,
editData,
index,
),
),
vm.isEmptyInvoice
? RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: "New Invoice",
style: myTheme.textTheme(
"labelLarge", isDark),
),
],
),
)
: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: "Edit ",
style: myTheme.textTheme(
"labelLarge", isDark),
),
TextSpan(
text: "#",
style: myTheme.textTheme(
"labelSmall", isDark),
),
TextSpan(
text: vm.selectedItem.id,
style: myTheme.textTheme(
"labelLarge", isDark),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 24.0),
child: Text(
'Bill From',
style: myTheme.textTheme("caption", isDark),
),
),
Form(
key: const Key('form'),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
// InvoiceFadeWidget(
// myTheme: myTheme,
// key: const Key('fadeWidget'),
// ),
InvoicesPageTextField(
class ItemListWidget extends StatelessWidget {
const ItemListWidget({
Key? key = const Key('ItemListWidget'),
required this.context,
required this.myTextTheme,
required this.myTextColor,
required this.value,
required this.myTheme,
required this.invoicesContents,
}) : super(key: const Key('ItemListWidget'),);
final BuildContext context;
final TextTheme myTextTheme;
final ThemeData myTextColor;
final ItemContents value;
final DarkThemeProvider myTheme;
final InvoicesContents invoicesContents;
#override
Widget build(BuildContext context) {
return Column(
children: [
for (var i = 0; i < invoicesContents.items.length; i++)
Padding(
padding: EdgeInsets.only(bottom: myTheme.normalPadding * 2),
child: Column(
children: [
Padding(
padding: EdgeInsets.only(bottom: myTheme.normalPadding),
child: InvoicesPageTextField(
key: const Key('ItemName'),
labelText: 'Item Name',
controller: TextEditingController(
text: invoicesContents.items[i].name,
),
onChanged: ((p0) {
invoicesContents.items[i].name = p0;
}),
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: myTheme.normalPadding * 3 - 8,
child: InvoicesPageTextField(
key: const Key('Qty'),
labelText: 'Qty.',
controller: TextEditingController(
text: invoicesContents.items[i].quantity.toString(),
),
onChanged: (p0) {
int? qty = int.tryParse(p0);
if (qty != null && qty > 0) {
invoicesContents.items[i].quantity = qty;
invoicesContents.items[i].total =
// ignore: unnecessary_cast
(qty *
invoicesContents.items[i].price.toDouble());
invoicesContents.total = invoicesContents.items
.map((e) => e.total)
.reduce((a, b) => a + b);
context.read<ItemContents>().refresh();
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Theme.of(context).canvasColor,
title: Text(
'Ops! =v=',
style: myTheme.textTheme(
"bodyMedium", myTheme.darkTheme),
),
content: Text(
'Natural Number Required !',
style: myTheme.textTheme(
"displayLarge", myTheme.darkTheme),
),
actionsPadding: EdgeInsets.symmetric(
horizontal: myTheme.normalPadding / 2 - 2,
vertical: myTheme.normalPadding / 2 - 2,
),
actions: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(myTheme.normalPadding),
),
color: myTextColor.highlightColor,
),
width: myTheme.normalPadding * 4 - 7,
height: myTheme.normalPadding * 2,
child: GestureDetector(
key: const Key('EnterNaturalNum'),
behavior: HitTestBehavior.opaque,
onTap: () => Navigator.pop(context),
child: Container(
alignment: Alignment.center,
child: Text(
'OK',
style: myTheme.textTheme(
"headlineMedium", myTheme.darkTheme),
),
),
),
),
],
),
);
}
},
),
),
InvoicePriceWidget(
key: const Key('InvoicePrice'),
context: context,
i: i,
invoicesContents: invoicesContents,
myTheme: myTheme),
InvoiceTotalWidget(
key: const Key('InvoiceTotal'),
myTextTheme: myTextTheme,
i: i,
context: context,
myTheme: myTheme,
invoicesContents: invoicesContents),
],
)
],
),
),
],
);
}
}
class InvoiceTotalWidget extends StatelessWidget {
const InvoiceTotalWidget({
Key? key = const Key('InvoiceTotal'),
required this.myTextTheme,
required this.i,
required this.context,
required this.myTheme,
required this.invoicesContents,
}) : super(
key: const Key('InvoiceTotal'),
);
final TextTheme myTextTheme;
final int i;
final BuildContext context;
final DarkThemeProvider myTheme;
final InvoicesContents invoicesContents;
#override
Widget build(BuildContext context) {
return Expanded(
key: const Key('TotalWidget'),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(bottom: myTheme.normalPadding),
child: Text(
'Total',
style: myTheme.textTheme("captionSmall", myTheme.darkTheme),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
context.read<ItemContents>().parseTotal(
invoicesContents.items[i].total.toDouble(),
showCurrency: false),
style: myTheme.textTheme("displayLarge", myTheme.darkTheme),
),
GestureDetector(
key: const Key('DeleteItem'),
onTap: () {
//const Key('DeleteItemOnTap');
invoicesContents.total = invoicesContents.total -
invoicesContents.items[i].quantity *
invoicesContents.items[i].price;
invoicesContents.items[i].quantity = 0;
invoicesContents.items[i].price = 0;
context.read<ItemContents>().refresh();
invoicesContents.items.removeAt(i);
context
.read<ItemContents>()
.data[context.read<ItemContents>().data.length - 1] =
invoicesContents;
context.read<ItemContents>().refresh();
},
child: Padding(
key: const Key('DeleteIcon'),
padding: EdgeInsets.only(right: myTheme.normalPadding * 0.9),
child: SvgPicture.asset(
'assets/images/icon-delete.svg',
),
),
),
],
),
],
),
);
}
}
I have tried to move the position of this button to the front of the ListView to ensure that the button has been generated. But this still does not make the button visible.

Related

I wants to reload and update the widget in flutter when clicked on action button of snack bar

This is my code where I am showing list of pending invoices.But when i accept the it should reload the pending invoice widget after clicking on snack bar action button.When user clicks on accept button its showing snack bar and when pressed accept action button snack bar it navigate back to same widget where I needs to update that widget along with new list of pending invoices without the invoice which we have accepted
class PendingInvoiceWidget extends StatelessWidget {
final double maxWidth;
final double maxHeight;
final bool isOverdue;
final String amount;
final String savedAmount;
final String invoiceDate;
final String dueDate;
Invoice fullDetails;
int index;
bool userConsentGiven = false;
final String companyName;
PendingInvoiceWidget(
{required this.fullDetails,
required this.maxWidth,
required this.maxHeight,
required this.amount,
required this.savedAmount,
required this.index,
required this.invoiceDate,
required this.dueDate,
required this.companyName,
required this.isOverdue});
#override
Widget build(BuildContext context) {
TextEditingController acceptController = TextEditingController();
TextEditingController rejectController = TextEditingController();
List<Invoice> pendingInvoice =
Provider.of<TransactionManager>(context).pendingInvoice;
String? invoiceId = fullDetails.sId;
String invAmt = double.parse(amount).toStringAsFixed(2);
DateTime id = DateTime.parse(invoiceDate);
DateTime dd = DateTime.parse(dueDate);
DateTime currentDate = DateTime.now();
Duration dif = dd.difference(currentDate);
int daysLeft = dif.inDays;
String idueDate = DateFormat("dd-MMM-yyyy").format(dd);
String invDate = DateFormat("dd-MMM-yyyy").format(id);
double h1p = maxHeight * 0.01;
double h10p = maxHeight * 0.1;
double w10p = maxWidth * 0.1;
return ProgressHUD(child: Builder(builder: (context) {
final progress = ProgressHUD.of(context);
return ExpandableNotifier(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: w10p * .5, vertical: 10),
child: Expandable(
collapsed: ExpandableButton(
child: Card(
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colours.offWhite,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
isOverdue
? Padding(
padding: EdgeInsets.symmetric(
vertical: h1p * 1),
child: Container(
// height: h1p * 4.5,
// width: w10p * 1.7,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(5),
color: Colours.failPrimary,
),
child: const Center(
child: Padding(
padding: EdgeInsets.all(4),
child: Text(
"Overdue",
style: TextStyles.overdue,
),
),
),
),
)
: Container(),
Row(
children: [
Text(
"${fullDetails.invoiceNumber}",
style: TextStyles.textStyle6,
),
SvgPicture.asset(
"assets/images/home_images/arrow-circle-right.svg"),
],
),
expanded: Column(
children: [
ExpandableButton(
child: Card(
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colours.offWhite,
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
isOverdue
? Text(
"$daysLeft days Overdue",
style: TextStyles.textStyle57,
)
: Text(
"$daysLeft days left",
style: TextStyles.textStyle57,
),
Text(
"₹ $invAmt",
style: TextStyles.textStyle58,
),
],
)
]),
),
),
),
Card(
elevation: .5,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 4,
),
const Text(
"Invoice Date",
style: TextStyles.textStyle62,
),
Text(
invDate,
style: TextStyles.textStyle63,
),
// Text(
// companyName,
// style: TextStyles.companyName,
// ),
],
),
SvgPicture.asset("assets/images/arrow.svg"),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const SizedBox(
height: 4,
),
const Text(
"Due Date",
style: TextStyles.textStyle62,
),
Text(
idueDate,
style: TextStyles.textStyle63,
),
// Text(
// companyName,
// style: TextStyles.companyName,
// ),
],
),
],
),
),
),
Card(
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 4,
),
const Text(
"Invoice Amount",
style: TextStyles.textStyle62,
),
Text(
"₹ $invAmt",
style: TextStyles.textStyle65,
)
// Text("Asian Paints",style: TextStyles.textStyle34,),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const SizedBox(
height: 4,
),
isOverdue
? const Text(
"Payabe Amount",
style: TextStyles.textStyle62,
)
: const Text(
"Pay Now",
style: TextStyles.textStyle62,
),
Text(
"₹ $invAmt",
style: TextStyles.textStyle66,
),
],
),
],
),
SizedBox(
height: h1p * 1.5,
),
// Row(
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// isOverdue?
// const Text(
// "Interest",
// style: TextStyles.textStyle62,
// ):
// const Text(
// "You Save",
// style: TextStyles.textStyle62,
// ),
// Text(
// "₹ $savedAmount",
// style:isOverdue?
// TextStyles.textStyle73:
// TextStyles.textStyle77,
// ),
// ],
// ),
// ],
// ),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: GestureDetector(
onTap: () {
progress!.show();
getIt<TransactionManager>()
.changeSelectedInvoice(fullDetails);
progress.dismiss();
Navigator.pushNamed(context, savemoreDetails);
},
child: Image.asset(
"assets/images/viewetails.png")),
),
Row(
children: [
Expanded(
child: InkWell(
onTap: () async {
showDialog(
context: context,
builder: (context) => Padding(
padding:
const EdgeInsets.symmetric(
// vertical: h10p * 5,
),
child: AlertDialog(
shape:
const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(
Radius.circular(
10.0))),
title: Row(
children: const [
Center(
child: Text("Comment"),
),
],
),
const SizedBox(
width: 10,
),
Expanded(
child: InkWell(
onTap: () async {
showDialog(
context: context,
builder: (context) => Padding(
padding:
const EdgeInsets.symmetric(
// vertical: h10p * 4,
),
child: StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
shape:
const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(
Radius.circular(
10.0))),
title: const Center(
child: Text("Consent"),
),
content: Column(
mainAxisSize:
MainAxisSize.min,
children: [
Text(
"I agree and approve Xuriti and NBFC Ditya Finance Private Limited to disburse funds to yhe seller $companyName for invoice number -${fullDetails.invoiceNumber} on my behalf"),
TextField(
controller:
acceptController,
decoration:
const InputDecoration(
hintText:
"Leave a comment *"),
onChanged: (_) {
print(acceptController
.text);
acceptController
.text.isEmpty
? Row(
children: const [
Text(
"Please write a reason",
style: TextStyle(
color:
Colors.red),
),
],
)
: Container();
},
),
SizedBox(
height: h1p * 4,
),
InkWell(
onTap: () async {
userConsentGiven =
true;
String timeStamp =
DateTime.now()
.toString();
if (acceptController
.text
.isNotEmpty) {
progress!.show();
String? message = await getIt<
TransactionManager>()
.changeInvoiceStatus(
invoiceId,
"Confirmed",
index,
fullDetails,
timeStamp,
userConsentGiven,
acceptController
.text,
"This invoice has been confirmed and Xuriti and its financing partner is authorised to disburse funds to the seller as per the invoice generated on my behalf");
progress.dismiss();
ScaffoldMessenger
.of(context)
.showSnackBar(
SnackBar(
behavior:
SnackBarBehavior
.floating,
content:
Text(
message!,
style:
const TextStyle(color: Colors.green),
)));
} else {
Fluttertoast.showToast(
msg:
"Please write a reason",
textColor:
Colors.red);
}
Navigator.pop(
context,
);
},
child: Container(
height: h1p * 8,
width: w10p * 7.5,
decoration: BoxDecoration(
borderRadius:
BorderRadius
.circular(
6),
color: Colours
.pumpkin),
child: const Center(
child: Text(
"Accept",
style: TextStyles
.subHeading,
)),
),
),
],
),
);
}),
));
},
child: Container(
height: h1p * 9,
decoration: BoxDecoration(
color: Colours.successPrimary,
borderRadius: BorderRadius.circular(5)),
child: const Center(
child: Text(
"Accept",
style: TextStyles.textStyle46,
),
),
),
),
),
],
)
],
),
),
),
],
)),
),
);
}));
}
}
Please try to use this code as SnackBar and decorate you want,
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
behavior: SnackBarBehavior.floating,
content: Text(
message!,
style: const TextStyle(color: Colors.green),
),
backgroundColor: Colors.black,
action: SnackBarAction(
label: "Reload",
onPressed: () {
setState(() {});
})));
I Hope these things are solve your issue,

Flutter 3: Row layout not showing up in a Stepper

I am working on an onboarding screen where I want to have the onboarding in 3 steps, so using the Stepper widget. The Stepper widget is inside a Column as I want to have some text displayed over the Stepper first. But now when I am trying to use a Row inside the Step widget to display some data horizontally, it does not show up. But it works if I make it a Column.
What could be causing this and any possible fix?
Flutter version: 3.3.8
What I am trying:
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 40),
const Text('Hi there!', style: AppStyles.heading),
const Text(
'Let\'s get you started',
style: AppStyles.subheading,
),
const SizedBox(
height: 50,
),
Stepper(
type: StepperType.vertical,
currentStep: _currentStep,
physics: const ScrollPhysics(),
onStepTapped: (step) => onTapped(step),
onStepContinue: onContinued,
onStepCancel: onCancel,
steps: [
Step(
title: const Text('Select a book'),
content: CustomButton(onPressed: () {}, text: 'Find Book'),
),
Step(
title: const Text('Set your goal'),
content: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const TextField(
decoration: InputDecoration(
hintText: 'Pages',
),
keyboardType: TextInputType.number,
),
const SizedBox(width: 10),
CustomButton(onPressed: () {}, text: 'Set Goal'),
],
)),
const Step(
title: Text('When you want to be reminded'),
content: TimePickerDialog(
initialTime: TimeOfDay(hour: 8, minute: 0),
))
],
controlsBuilder: (context, _) {
return Row(
children: <Widget>[
TextButton(
onPressed: () => onContinued(),
child: const Text('Next'),
),
TextButton(
onPressed: () => onCancel(),
child: const Text('Back'),
),
],
);
},
)
],
),
),
),
);
}
CustomButton widget:
class CustomButton extends StatelessWidget {
final String text;
final bool isOutlined;
final bool isLoading;
final bool isDisabled;
final VoidCallback onPressed;
const CustomButton(
{Key? key,
required this.text,
this.isOutlined = false,
this.isLoading = false,
this.isDisabled = false,
required this.onPressed})
: super(key: key);
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
backgroundColor: isOutlined ? Colors.white : Pallete.primaryBlue,
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 18),
foregroundColor: isOutlined ? Pallete.primaryBlue : null,
elevation: 4,
side: isOutlined
? const BorderSide(color: Pallete.primaryBlue, width: 1.0)
: null,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
child: Stack(
children: [
Visibility(
visible: isLoading ? false : true,
child: Text(text,
style: const TextStyle(
fontSize: 18.0, fontWeight: FontWeight.w600)),
),
Visibility(
visible: isLoading,
child: Loader(
color: isOutlined ? Pallete.primaryBlue : Pallete.white,
))
],
),
);
}
}
Output
Wrap your TextField and CustomButton (while it has ElevatedButton) with Expanded widget.
Step(
title: const Text('Set your goal'),
content: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: const TextField(
decoration: InputDecoration(
hintText: 'Pages',
),
keyboardType: TextInputType.number,
),
),
const SizedBox(width: 10),
Expanded(
child: CustomButton(
onPressed: () {}, text: 'Set Goal')),
],
)),
Find more about constraints

Flutter multiple ExpansionTile's collapses simultaneously

I have a custom Card widget with ExpansionTile as a child which displays multiple Dropdownbuttons according to data fetched from an API.
But when I use ListView.builder to build N amount of said custom widgets they all behave simultaneously, for example when I collapse the ExpansionTile all open ExpansionTiles collapse simultaneously and reset the data inside Dropdownbuttons (resetting the data expected outcome when ExpansionTile collapsed but only the collapsed ExpansionTile should reset its children Dropdownbuttons, not all open ExpansionTiles children).
Here is my builder.
var items = ["Apartment 1", "Apartment 2", "Apartment 3", "Apartment 4"];
class MapPage extends StatelessWidget {
const MapPage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
key: ValueKey(items),
scrollDirection: Axis.vertical,
itemCount: items.length,
padding: const EdgeInsets.only(top: 8),
itemBuilder: (context, index) {
return MapCard(
building: items[index],
floor: 4,
key: Key(items[index].toString()),
);
}),
);
}
}
and my CustomCard
class MapCard extends StatefulWidget {
final String building;
final int floor;
const MapCard({super.key, required this.building, required this.floor});
#override
State<MapCard> createState() => _MapCardState();
}
class _MapCardState extends State<MapCard> {
#override
Widget build(BuildContext context) {
PageStorageKey key = PageStorageKey('${widget.key}');
return Center(
child: Consumer<MapCardViewModel>(
builder: (context, mapCardViewModel, child) => Card(
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: Padding(
padding: const EdgeInsets.only(bottom: 12),
child: ExpansionTile(
key: key,
onExpansionChanged: (changed) {
if (!changed) {
mapCardViewModel.setAreaVisibility(false);
mapCardViewModel.setButtonVisibility(false);
mapCardViewModel.setIsFloorChosen(false);
mapCardViewModel.setAreaVisibility(false);
mapCardViewModel.area = mapCardViewModel.areas[0];
mapCardViewModel.floorNumber = mapCardViewModel.floors[0];
}
},
title: Row(
children: [
Container(
padding:
const EdgeInsets.only(top: 8, bottom: 8, right: 8),
child: Image.asset(
"assets/images/example.png",
height: 80,
width: 80,
)),
Flexible(
child: Container(
padding: const EdgeInsets.fromLTRB(0, 8, 8, 8),
child: Column(
children: [
Text("${widget.building} Apartment \n"
"Floor Count ${widget.floor} ")
],
),
),
)
],
),
children: [
const Text("Choose Floor"),
Padding(
padding: const EdgeInsets.only(right: 24, left: 24),
child: DropdownButton(
isExpanded: true,
value: mapCardViewModel.isFloorChosen == false
? mapCardViewModel.floors[0]
: mapCardViewModel.floorNumber,
items: mapCardViewModel.floors
.map<DropdownMenuItem<int>>((int i) {
return DropdownMenuItem<int>(
value: i,
child: Text(i.toString()),
);
}).toList(),
onChanged: (int? value) {
mapCardViewModel.setFloorNumber(value!);
mapCardViewModel.setIsFloorChosen(true);
mapCardViewModel.setAreaVisibility(true);
}),
),
Visibility(
visible: mapCardViewModel.isAreaVisible,
child: Column(
children: [
const Text("Choose an Area to map"),
Padding(
padding: const EdgeInsets.only(right: 24, left: 24),
child: DropdownButton(
isExpanded: true,
value: mapCardViewModel.isAreaChosen == false
? mapCardViewModel.areas[0]
: mapCardViewModel.area,
items: mapCardViewModel.areas
.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
mapCardViewModel.setArea(value!);
mapCardViewModel.setIsAreaChosen(true);
mapCardViewModel.setButtonVisibility(true);
}),
),
],
),
),
Visibility(
visible: mapCardViewModel.isButtonsVisible,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return CustomDialog(
title: "Mapping Status",
content:
"This area hasn't been mapped yet",
page: Container(),
buttonColor: MainColors().mainBlue);
});
},
child: const Text("Show Area Map")),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const MappedPage(),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: MainColors().mainBlue),
child: const Text(
"Map The Area",
style: TextStyle(color: Colors.white),
))
],
),
)
],
),
)),
),
));
}
}
I tried to assign keys to each ExpansionTile and custom MapCard widgets with StatefulWidget but I couldn't solve my problem.
I remove all 3rd dependency and try to adjust your solution of MapCard widget.
var items = ["Apartment 1", "Apartment 2", "Apartment 3", "Apartment 4"];
final floors = ['Floor 1', 'Floor 2'];
final areas = ['Area 1', 'Area 2'];
class MapCard extends StatefulWidget {
final String building;
final int floor;
const MapCard({Key? key, required this.building, required this.floor}): super(key: key);
#override
State<MapCard> createState() => _MapCardState();
}
class _MapCardState extends State<MapCard> {
#override
Widget build(BuildContext context) {
PageStorageKey key = PageStorageKey('${widget.key}');
return Center(
child: Card(
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: Padding(
padding: const EdgeInsets.only(bottom: 12),
child: ExpansionTile(
key: key,
onExpansionChanged: (changed) {
if (!changed) {
print('!changed');
}
},
title: Row(
children: [
Container(
padding:
const EdgeInsets.only(top: 8, bottom: 8, right: 8),
child: Placeholder(
fallbackHeight: 80,
fallbackWidth: 80,
)),
Flexible(
child: Container(
padding: const EdgeInsets.fromLTRB(0, 8, 8, 8),
child: Column(
children: [
Text("${widget.building} Apartment \n"
"Floor Count ${widget.floor} ")
],
),
),
)
],
),
children: [
const Text("Choose Floor"),
Padding(
padding: const EdgeInsets.only(right: 24, left: 24),
child: DropdownButton(
isExpanded: true,
value: floors.first,
items: floors
.map<DropdownMenuItem<String>>((String i) {
return DropdownMenuItem<String>(
value: i,
child: Text(i.toString()),
);
}).toList(),
onChanged: (String? value) {
// code here
}),
),
Visibility(
visible: true,
child: Column(
children: [
const Text("Choose an Area to map"),
Padding(
padding: const EdgeInsets.only(right: 24, left: 24),
child: DropdownButton(
isExpanded: true,
value: areas.first,
items: areas
.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
// code here
}),
),
],
),
),
Visibility(
visible: true,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(content: Text('Alert dialog'),);
});
},
child: const Text("Show Area Map")),
ElevatedButton(
onPressed: () {
print('navigate');
},
child: const Text(
"Map The Area",
style: TextStyle(color: Colors.white),
))
],
),
)
],
),
)),
),
);
}
}
But I suppose your problem in the line
Consumer<MapCardViewModel>
because every time when you change it this will apply to each created card. So you have to put it above your ListView.builder and pass it as a parameter to your cards

Text overflow flutter

I have the next widget, which is rendered with overflow. I have tried to solve, but i don't know. Can anyone help me? The aim is to do a custom card inside listview.
I have tried to wrap with expanded buth then, the error is referenced with constraints.
import 'package:flutter/material.dart';
import '../../shared/AppTheme.dart';
class ComandaScreen extends StatefulWidget {
const ComandaScreen({Key? key}) : super(key: key);
#override
State<ComandaScreen> createState() => _ComandaScreenState();
}
class _ComandaScreenState extends State<ComandaScreen> {
bool expanded = false;
int unidades = 0;
final List<Map<String, dynamic>> _items = List.generate(
10, (index) => {'id': index, 'Nombre': 'Nuggets $index',
'isExpanded': false, "unidades": 8});
#override
Widget build(BuildContext context) {
final ButtonStyle flatButtonStyle = TextButton.styleFrom(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
);
return Scaffold(
appBar: AppBar(
title: const Text('Comanda'),
backgroundColor: AppTheme.backgroundColor,
foregroundColor: AppTheme.primaryTextColor,
elevation: 0,
),
body: SingleChildScrollView(
child: ExpansionPanelList(
elevation: 3,
// expandedHeaderPadding: const EdgeInsets.all(10),
expansionCallback: (index, isExpanded) {
setState(() {
_items[index]['isExpanded'] = !isExpanded;
});
},
animationDuration: const Duration(milliseconds: 200),
children: _items
.map(
(item) => ExpansionPanel(
canTapOnHeader: true,
// backgroundColor: item['isExpanded'] == true ? Colors.cyan[100] : Colors.white,
headerBuilder: (context, isExpanded) {
return Container(
margin: const EdgeInsets.all(10),
child: Row(children: [
const CircleAvatar(
child: Text(
'1',
textAlign: TextAlign.center,
)),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Nuggets',
style: TextStyle(color: Colors.black, fontWeight: FontWeight.w600),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: const [
Text(
'Unidades: ${7}',
style: TextStyle(color: Colors.black),
),
Text(
'Pendientes: 400',
style: TextStyle(color: Colors.black),
),
],
),
const SizedBox(
width: 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'Precio: 10 €',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.black),
),
Text(
'Total: 70 €',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.black),
),
],
),
],
),
],
),
),
]),
);
},
body: ButtonBar(
alignment: MainAxisAlignment.spaceAround,
buttonHeight: 52.0,
buttonMinWidth: 90.0,
children: <Widget>[
TextButton(
style: flatButtonStyle,
onPressed: () {
setState(() {
item['unidades'] += 1;
});
},
child: Column(
children: const <Widget>[
Icon(
Icons.add,
color: AppTheme.grismedio,
),
// Padding(
// padding: EdgeInsets.symmetric(vertical: 2.0),
// ),
// Text('Más'),
],
),
),
TextButton(
style: flatButtonStyle,
onPressed: () {
setState(() {
item['unidades'] -= 1;
});
},
child: Column(
children: const <Widget>[
Icon(
Icons.remove,
color: AppTheme.grismedio,
),
// Padding(
// padding: EdgeInsets.symmetric(vertical: 2.0),
// ),
// Text('Menos'),
],
),
),
TextButton(
style: flatButtonStyle,
onPressed: () {},
child: Column(
children: const <Widget>[
Icon(
Icons.edit_outlined,
color: AppTheme.grismedio,
),
// Padding(
// padding: EdgeInsets.symmetric(vertical: 2.0),
// ),
// Text('Editar'),
],
),
),
TextButton(
style: flatButtonStyle,
onPressed: () {},
child: Column(
children: const <Widget>[
Icon(
Icons.delete_outline_outlined,
color: AppTheme.grismedio,
),
// Padding(
// padding: EdgeInsets.symmetric(vertical: 2.0),
// ),
// Text('Eliminar'),
],
),
),
TextButton(
style: flatButtonStyle,
onPressed: () {},
child: Column(
children: const <Widget>[
Icon(
Icons.card_giftcard_outlined,
color: AppTheme.grismedio,
),
// Padding(
// padding: EdgeInsets.symmetric(vertical: 2.0),
// ),
// Text('Invitar'),
],
),
)
],
),
isExpanded: item['isExpanded'],
),
)
.toList(),
// Card_lineaComanda(flatButtonStyle),
),
),
);
}
}
I 've edited the code to show all screen widget.
Image of result of code before:
For desktop applications, you can prevent the resize with breakpoint, so the error won't happen. In the pubsec.yaml file, add the following dependency.
window_size:
git:
url: https://github.com/google/flutter-desktop-embedding.git
path: plugins/window_size
And in your main method before runapp add this code with min-width and min-height below which the app won't resize.
const double desktopMinWidth = 800.0;
const double desktopMinHeight = 600.0;
if (Platform.isMacOS || Platform.isWindows) {
setWindowMinSize(const Size(desktopMinWidth, desktopMinHeight));
setWindowMaxSize(Size.infinite);
}
Note: Once done restart your app.
For mobile, it is entirely a different case. You might need to restructure the design

InteractiveViewer does not hide other Widgets when panned

I used InteractiveViewer here on my ProductDetail Page.
But when I pan and Zoom the image, the other widgets such as buttons and text are coming over it.
Here is my code:
class ProductDetail extends StatelessWidget {
final Product product;
ProductDetail({this.product});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
children: [
SizedBox(
height: 20
),
SizedBox(
height: 300,
width: MediaQuery.of(context).size.width -20,
child: InteractiveViewer(
child: Hero(
tag: product.id,
child: Carousel(
autoplay: false,
boxFit: BoxFit.cover,
dotBgColor: Colors.transparent,
dotColor: Colors.black.withOpacity(0.5),
images: [
AssetImage(product.imageUrl),
AssetImage(product.imageUrl),
AssetImage(product.imageUrl),
],
),
),
),
),
SizedBox(
width: MediaQuery.of(context).size.width -20,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text(
'₹ ${product.price}',
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
Text(
'₹ ${product.price}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
decoration: TextDecoration.lineThrough,
color: Colors.grey
),
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
SizedBox(
child: ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(getColorFromHex('#d1d1d1')),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.add_shopping_cart),
Text("Add to Cart", style: TextStyle(fontSize: 18)),
],
),
)
),
SizedBox(
child: ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(getColorFromHex('#56a8e2')),
),
child: Row(
children: [
Icon(Icons.shopping_bag),
Text("Buy Now", style: TextStyle(fontSize: 18)),
],
),
)
),
],
),
),
],
),
),
);
}
}
How do I hide the other widgets like Text and buttons when I pan or zoom the image with the InteractiveViewer?
InteractiveViewer has two properties onInteractionStart and onInteractionEnd.
onInteractionStart → void Function(ScaleStartDetails details) : Called
when the user begins a pan or scale gesture on the widget.
onInteractionEnd → void Function(ScaleEndDetails details) : Called
when the user ends a pan or scale gesture on the widget.
You need to wrap the widgets that you wish to hide with the Visibility widget.
You can set - reset the visibility of the widgets wrapped under Visibility widget when user starts (onInteractionStart) and stops to pan or scale (onInteractionEnd).
Please see the code below, I had to make some changes to make it work :
import 'package:carousel_pro/carousel_pro.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
home: MyApp(),
));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ProductDetail(),
),
);
}
}
class ProductDetail extends StatefulWidget {
ProductDetail({products});
#override
_ProductDetailState createState() => _ProductDetailState();
}
class _ProductDetailState extends State<ProductDetail> {
bool _visible = true;
TransformationController controller = TransformationController();
final Products products = Products(
id: 10,
imageUrl:
"https://cdn.pixabay.com/photo/2020/10/01/17/11/temple-5619197_960_720.jpg",
price: 20.00);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
children: [
SizedBox(height: 20),
SizedBox(
height: 300,
width: MediaQuery.of(context).size.width - 20,
child: InteractiveViewer(
transformationController: controller,
onInteractionStart: (scale) {
setState(() => _visible = false);
},
onInteractionEnd: (scale) {
setState(() {
controller.value = Matrix4.identity();
_visible = true;
});
},
child: Hero(
tag: products.id,
child: Carousel(
autoplay: false,
boxFit: BoxFit.cover,
dotBgColor: Colors.transparent,
dotColor: Colors.black.withOpacity(0.5),
images: [
NetworkImage(products.imageUrl),
NetworkImage(products.imageUrl),
NetworkImage(products.imageUrl),
],
),
),
),
),
Visibility(
visible: _visible,
child: SizedBox(
width: MediaQuery.of(context).size.width - 20,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text(
'₹ ${products.price.toString()}',
style: TextStyle(
fontSize: 25, fontWeight: FontWeight.bold),
),
Text(
'₹ ${products.price.toString()}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
decoration: TextDecoration.lineThrough,
color: Colors.grey),
),
],
),
),
),
),
Visibility(
visible: _visible,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
SizedBox(
child: ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(Colors.cyanAccent),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.add_shopping_cart),
Text("Add to Cart", style: TextStyle(fontSize: 18)),
],
),
)),
SizedBox(
child: ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(Colors.limeAccent),
),
child: Row(
children: [
Icon(Icons.shopping_bag),
Text("Buy Now", style: TextStyle(fontSize: 18)),
],
),
)),
],
),
),
),
],
),
),
);
}
}
class Products {
final int id;
final String imageUrl;
final double price;
Products({
this.id,
this.imageUrl,
this.price,
});
Products copyWith({
int id,
String imageUrl,
double price,
}) {
return Products(
id: id ?? this.id,
imageUrl: imageUrl ?? this.imageUrl,
price: price ?? this.price,
);
}
}