How can i show a modal below another modal in Flutter? - flutter

So, i'm currently developing a "to-do list" app using Flutter. This app has a floating button that, when pressed, shows a modal bottom sheet with a few other buttons. Some of those buttons, when pressed, also returns modal bottom sheets with options for the user to choose from. The thing is, i can't seem to find a way to place the secondary bottom sheet directly below the primary bottom sheet. In other words, i want to make the primary modal resize to avoid being overlapped by the secondary modal. Is that possible on flutter?
Here's what the app should look like
And here's what it currently looks like
Here's the code example for the primary modal bottom sheet:
taskModal(BuildContext context) {
return showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(20), topLeft: Radius.circular(20)),
color: Colors.white,
),
child: Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_buildCancelButton(),
TaskForm(),
BuildBorder(),
PriorityButton(),
BuildBorder(),
DateButton(),
BuildBorder(),
_buildConfirmButton(context)
],
),
),
),
);
},
);
}
And here is the code example for one of the buttons i've mentioned before (the priority button, specifically):
class PriorityButton extends StatefulWidget {
#override
_PriorityButtonState createState() => _PriorityButtonState();
}
class _PriorityButtonState extends State<PriorityButton> {
List<String> _priorities = [
'Nenhuma',
'Baixa (!)',
'Média (!!)',
'Alta (!!!)',
];
String _settledPriority = 'Selecionar';
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child: Icon(
Icons.flag,
color: Color(0xff9DA1A6),
)),
Text("Prioridade"),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.only(
right: MediaQuery.of(context).size.width * 0.04),
child: Text(_settledPriority,
maxLines: 1),
),
),
),
],
),
onTap: () async => await _buildBottomSheet(),
);
}
_setPriority(String priority) {
setState(() {
_settledPriority = priority;
});
Navigator.pop(context);
}
_buildBottomSheet() {
return showModalBottomSheet(
context: context,
builder: (context) {
return Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: _priorities.length,
itemBuilder: (context, index) => GestureDetector(
child: Text(
_priorities\[index\],
),
onTap: () => _setPriority(_priorities\[index\]),
),
),
);
},
);
}
}

What you can do here is fetch the height of the bottom modal with LayoutBuilder, then use this value as padding for the first displayed modal. Though by design, the modal seems to appear in a single BottomSheet. Another approach that you can look into is by populating a single modal instead of using two modal on the screen.

Related

How to implement Popup with Flutter?

I have a Flutter app with screens rendered conditionally with an array. Anyway, I need to have a popup screen like this :
If have stored all my "popup screens" in an array and rendered the main screen and the popup screen in a stack. I don't know if this is the best solution and I guess I will have performance issues.
Here is the PopupContainerclass, this Widget is rendered on every Popup Screen with the child passed as content :
class PopupContainer extends StatefulWidget {
final Widget? child;
const PopupContainer({
Key? key,
this.child,
}) : super(key: key);
#override
State<PopupContainer> createState() => _PopupContainerState();
}
class _PopupContainerState extends State<PopupContainer> {
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return Consumer<ScreenManager>(
builder: (context, manager, child) => Stack(
alignment: Alignment.bottomCenter,
children: [
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: Container(
decoration: BoxDecoration(color: Colors.white.withOpacity(0.0)),
),
),
Container(
height: height * 0.8,
width: double.infinity,
padding: const EdgeInsets.all(32),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
boxShadow: [
BoxShadow(
blurRadius: 37,
spreadRadius: 0,
color: Color.fromRGBO(28, 48, 72, 0.24),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
alignment: Alignment.topRight,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
primary: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () => manager.closePopup(),
child: SvgPicture.asset('assets/close.svg'),
),
),
widget.child ?? const SizedBox.shrink(),
],
),
),
],
),
);
}
}
The consumer is used for handling the screens states :
enum ScreensName {
homeScreen,
favoriteProductsScreen,
archivedListsScreen,
recipesScreen,
}
enum PopupsName {
newProductPopup,
archivedListPopup,
editProductPopup,
newRecipePopup,
}
const screens = <ScreensName, Widget>{
ScreensName.homeScreen: HomeScreen(),
ScreensName.favoriteProductsScreen: FavoriteProductsScreen(),
ScreensName.archivedListsScreen: ArchivedListsScreen(),
ScreensName.recipesScreen: RecipesScreen(),
};
const popups = <PopupsName, Widget>{
PopupsName.newProductPopup: NewProductPopup(),
};
class ScreenManager extends ChangeNotifier {
static ScreensName screenName = ScreensName.homeScreen;
static PopupsName? popupName = PopupsName.newProductPopup;
get currentScreen => screens[screenName];
get currentPopup => (popups[popupName] ?? Container());
/// Open the given popup.
void openPopup(PopupsName newPopupName) {
popupName = newPopupName;
notifyListeners();
}
/// Closes the current popup.
void closePopup() {
popupName = null;
notifyListeners();
}
/// Change the screen.
void setScreen(ScreensName newScreenName) {
screenName = newScreenName;
notifyListeners();
}
}
And finally, the main component build method (I also have some theme styling but useless here) :
Widget build(BuildContext context) {
DatabaseHelper.initDb();
return Consumer<ScreenManager>(
builder: (context, screenManager, child) => Material(
child: MaterialApp(
title: _title,
theme: _customTheme(),
home: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
screenManager.currentScreen,
screenManager.currentPopup,
],
),
),
),
);
}
PS : I am a web developer so I know the main programming principles but Dart and mobile dev is brand new for me. Also, I could share my code with you, however, this project is splitted into files and it would take too much space in the post. Ask if you need it !
Maybe an easier solution would be to use the showDialog function where you need to trigger the popup. Check out the docs https://api.flutter.dev/flutter/material/showDialog.html
showDialog(context: context, builder: (context) => AlertDialog(title: Text('Title'), content: Text('Here content'),));

Overflow when using viewinsets in a modalButtomSheet

Problem:
I'm using MediaQuery and viewInsets to add Padding, when the user
triggers the keyboard in a modalBottomSheet.
It looks OK, but I get a message about overflow
When I draw down the modalBottomSheet manually, I can see the overflow happening behind the sheet.
Code first, then screenshots:
This is the GestureDetector opening the modal sheet:
GestureDetector(
onTap: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(23.r),
),
),
isScrollControlled: true,
context: context,
builder: (bctx) => StatefulBuilder(builder:
(BuildContext context, StateSetter setModalState) {
return ModalAddFavorite();
}));
},
This is the Widegt that I use as modal sheeet:
class ModalAddFavorite extends StatefulWidget {
const ModalAddFavorite({Key? key}) : super(key: key);
#override
_ModalAddFavoriteState createState() => _ModalAddFavoriteState();
}
class _ModalAddFavoriteState extends State<ModalAddFavorite> {
#override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setModalState) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom * 0.98.h),
//
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 20.h,
),
Container(
width: 80.w,
height: 6.w,
decoration: BoxDecoration(
color: Provider.of<CustomColors>(context, listen: false)
.customColorScheme['Grey 2'],
borderRadius: BorderRadius.circular(6.r),
),
),
SizedBox(
height: 25.h,
),
//
Text(
'ADD A FAVORITE',
style: Provider.of<CustomTextStyle>(context)
.customTextStyle('ModalHeader'),
),
SizedBox(
height: 25.5.h,
),
//
//
InputFieldAddFavorite(),
SizedBox(
height: 40.h,
)
],
),
),
);
});
}
}
Screenshots:
Modal Sheet open / keyboard inactive / no overflow
Modal sheet open / keyboard active / overflow warning in Flutter
Modal shett pulled back manually // overflow visible behind the sheet:
Try to add physics: NeverScrollableScrollPhysics() under your SingleChildScrollView().
Issue solved: Instead of wrapping the modal sheet in a SingleChildScrollView, I needed to wrap the Column that contains the page itself.

Flutter bottomSheet with input in a column and an attachment icon on the bottom, keyboard hides the icon when active

I am showing a bottom sheet with showModalBottomSheet like so:
showModalBottomSheet(
isScrollControlled: true,
context: context,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Container(
height: MediaQuery.of(context).size.height * .90,
width: double.infinity,
child: Column(
children: [
Expanded(
child: PostCreate(isImageType: isImageType)
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 0, 20),
child: Align(
alignment: Alignment.bottomLeft,
child: IconButton(
onPressed: () {
setState(() {
isImageType = true;
});
},
icon: Icon(Icons.attach_file_rounded),
)
),
)
],
),
);
}
);
}
);
The PostCreate widget contains a column with a * form (single input) and a section for adding the images. When the keyboard is not visible the attach_file_rounded Icon is where you would expect (bottom left), but when the input is focused and the keyboard is visible, the icon is hidden behind the keyboard. I'm not concerned about the code in PostCreate because if I change it to just a form/input in Expanded:
Expanded(
child: Form(
child: TextFormField(
autofocus: true,
)
)
)
I have the same exact problem. What's the way for me to align something on the bottom left of a bottom sheet that will dynamically sit above the keyboard when it is active? Another thing to note, is that I do want the PostCreate widget to expand, allowing room for certain things (like the attach_file icon) on the bottom of the sheet. That is all working as expected, I just need it to adjust with the keyboard.
I figured this out by adding padding: MediaQuery.of(context).viewInsets, to the Padding widget around the Align/IconButton widgets.
Padding(
padding: MediaQuery.of(context).viewInsets,
child: Align(
alignment: Alignment.bottomLeft,
child: IconButton(
onPressed: () {
setState(() {
isImageType = true;
});
},
icon: Icon(Icons.attach_file_rounded),
)
),
)

Flutter: using nested navigator

I have a page that divides the screen into left (CheckOutPage) and right (MyFoodOrder()):
class TakeOrderPage extends StatefulWidget {
#override
_TakeOrderPageState createState() => _TakeOrderPageState();
}
class _TakeOrderPageState extends State<TakeOrderPage> {
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(flex: 4, child: CheckOutPage()),
VerticalDivider(),
Expanded(flex: 6, child: MyFoodOrder()),
],
);
}
}
In MyFoodOrder, I have a widget that builds the food items using FoodCard:
Widget buildFoodList() {
return Expanded(
child: GridView.count(
//itemCount: foods.length,
childAspectRatio: 3.0,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
crossAxisCount: 2,
controller: _controller,
physics: BouncingScrollPhysics(),
//children: foods.map((food) {
// return FoodCard(food);
//}).toList(),
children: [for (var food in Level1) if ((food.foodType == MyFoodTypes[value])) FoodCard(food)].toList(),
),
);
}
Inside FoodCard, I have a widget that has an InkWell that can move to another page when tapped for selecting options. At the moment, the new page ChooseOptions() will occupy the whole screen:
Widget buildPriceInfo() {
ConfirmAction action;
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'\$ ${food.price}',
style: titleStyle,
),
Card(
margin: EdgeInsets.only(right: 0),
shape: roundedRectangle4,
color: mainColor,
child: InkWell(
onTap: IsAvailable() ? () async {
remark = ''; //cancel any selected taste
if (food.options.length != 0) {
if (food.options.containsKey('2')) {
action = await _showTasteDialog(food.index);
}
if (food.options.containsKey('1')) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChooseOptions(food)),
);
}
else
addItemToCard();
}
else
addItemToCard();
} : (){},
splashColor: Colors.white70,
customBorder: roundedRectangle4,
child: Icon(Icons.add, size: 30,),
),
)
],
),
);
}
I want to modify it so that the new page of ChooseOptions only occupies the area of MyFoodOrder() instead of the whole screen. I read that nested navigator is the solution but I couldn't work it out after reading some of the examples online. Grateful if more explicit guidance or help can be provided.
Many thanks!
Wrap your MyFoodOrder with Navigator, set the routes, and assign a Navigation Key to it.
static final navigatorKey = GlobalKey<NavigatorState>();
Then use the Navigation Key to changing the routing.
navigatorKey.currentState.pushNamed("Your route");

How can i turn container to Floatting action button

i am developin app i just got some of those code from an app i just need to cart page button make a floating action button this button shows how many products in cart also i want to do this if counter=0 i need to hide that FloatingActionButton if they added item to basket just show that time if you have any suggestions thanks a lot for now
'class CustomAppBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
final CartListBloc bloc = BlocProvider.getBloc<CartListBloc>();
// TODO: implement build
return Container(
margin: EdgeInsets.only(bottom: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
StreamBuilder(
stream: bloc.listStream,
builder: (context, snapshot) {
List<FoodItem> foodItems = snapshot.data;
int length = foodItems != null ? foodItems.length : 0;
return buildGestureDetector(length, context, foodItems);
},
)
],
),
);
}
GestureDetector buildGestureDetector(
int length, BuildContext context, List<FoodItem> foodItems) {
return GestureDetector(
onTap: () {
if (length > 0) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Cart()));
} else {
return;
}
},
child: Container(
margin: EdgeInsets.only(left: 30),
child: Text(length.toString()),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.yellow[800], borderRadius: BorderRadius.circular(50)),
),
);
}'
First, you need a stateful widget and not a stateless one. If you are using VS code, it can actually do it for you automatically.
Once you have that, one variable will be "length".
String length;
void initState() {
length=0;
}
Whenever you increment it up, you have to call...
setState(() {
length+=1;
});
or down...
setState(() {
length-=1;
});
your floating action button will be...
(length==0)?
Container():
GestureDetector(
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Cart()));
},
child: Container(
margin: EdgeInsets.only(left: 30),
child: Text(length.toString()),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.yellow[800], borderRadius: BorderRadius.circular(50)),
),
);
This means that if the cart length is 0, it will show an empty container.
If the cart is not 0, it will show a button with length