How to Change Swiping direction of CupertinoPageRoute - flutter

I’m using Cupertino Page Route to produce the following Behavior
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//doesn't do what I want to do
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(settingsPage),
onPanUpdate: (details){
if(details.delta.dx < 0){
Navigator.of(context).pushNamed(settingsPage);
}
},
child: Icon(Icons.settings),
),
//Not my concern for now
IconButton(
onPressed: () {},
icon: const Icon(Icons.notifications, color: Colors.black),
),
//Does what I want it to do
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(messages),
child: Icon(Icons.message),
),
],
),
It allows me to swipe from Left to Right to close the Message Screen
Unfortunately it does not allow me to close the Settings Screen by swiping from Right to Left
Anyone one knows how to change the direction of the swipe for CupertinoPageRoute or mimic it's behavior with an other Solution ?

I don’t think you can easily change the swiping direction of CupertinoRoute but I’ve found two easy solutions for you :
You could either use a PageView Widget - which allows you to swipe between screens
Or - better solution for your specific situation :
You could instead use the page_transition Package with a Swipe Direction Detector
This Second Solution will best reproduce the behavior of CupertinoPageRoute
in addition it will provide you with animations going from HomeScreen to Settings/Messages but also back to your HomeScreen :
This is how you could implement this second solution shown in the GIF above into your App :
import 'package:page_transition/page_transition.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
_HomeScreen createState() => _HomeScreen();
}
class _HomeScreen extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
automaticallyImplyLeading: false,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () => Navigator.push(
context,
//Use Page Transition left to right here
PageTransition(
// duration: Duration(seconds: 1),
type: PageTransitionType.leftToRightWithFade,
child: SettingsScreen(),
inheritTheme: true,
ctx: context),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: const Icon(Icons.settings, color: Colors.black),
)),
IconButton(
onPressed: () {},
icon: const Icon(Icons.notifications, color: Colors.black),
),
GestureDetector(
onTap: () => Navigator.push(
context,
//Use Page Transition right to left here
PageTransition(
// duration: Duration(seconds: 1),
type: PageTransitionType.rightToLeftWithFade,
child: const MessageScreen(),
inheritTheme: true,
ctx: context),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: const Icon(Icons.message, color: Colors.black),
)),
],
),
),
//body
body: Container(),
);
}
}
Then in your Settings/Messages Screens just implement a GestureDetector which will trigger on Swipe
It will Navigator.pop (close) your screen - & Flutter will take care of the reverse animation
class SettingsScreen extends StatefulWidget {
const SettingsScreen({Key? key}) : super(key: key);
#override
_SettingsScreen createState() => _SettingsScreen();
}
class _SettingsScreen extends State<SettingsScreen> {
#override
Widget build(BuildContext context) {
return GestureDetector(
// This is where you detect swiping from right to left
onPanUpdate: (details) {
if (details.delta.dx < -10) {
// Upon swiping detection it then pops the screen - reverse animation happens without additional code
Navigator.pop(context);
}
},
child: const Scaffold(
backgroundColor: Colors.blueGrey,
body: Center(child: const Text("🥸 Settings")),
));
}
}
// Same code here for your MessageScreen but with opposite swipe gesture detection (left to right)
class MessageScreen extends StatefulWidget {
const MessageScreen({Key? key}) : super(key: key);
#override
_MessageScreen createState() => _MessageScreen();
}
class _MessageScreen extends State<MessageScreen> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dx > 10) {
Navigator.pop(context);
}
},
child: const Scaffold(
backgroundColor: Colors.redAccent,
body: Center(child: const Text("😎 Messages")),
));
}
}
That's it ! - the only down side to this method is once the swipe has been detected there is no way to cancel it
---- upon long discussion with the user - comments bellow have been addressed above & question edited

Related

widget into List<Widget> does not update into build method even if i call setState

i have the following simple full code
import 'dart:developer';
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
List myListWidget = [];
late bool isColorWhie = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
setState(() {
myListWidget.add(
Container(
width: 50,
height: 50,
color: isColorWhie?Colors.white:Colors.red,
)
);
});
},
child: Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...myListWidget,
TextButton(
onPressed: (){
setState(() {
isColorWhie = !isColorWhie; // here never update
log('done');
});
},
child: const Text('tab to Change color',style: TextStyle(color: Colors.white),)
)
],
),
),
),
);
}
}
i tap on any point on screen to add Container into myListWidget thn call setState(() {}); to update ui.
everything fine now but when i change the isColorWhie to true it should change the color to white but it never update !
i am totally confused why it does not update ? And how could i handle with this ?
For base color change, I am using a separate button, also switching the list value.
One thing variable does update the UI, you need to handle state inside the item(state-management property) or reinitialize the variable to get update state.
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
List<bool> myListWidgetState = [];
bool isColorWhie = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(
() {
myListWidgetState.add(isColorWhie);
},
);
},
child: Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...myListWidgetState.map(
(e) {
return Container(
width: 50,
height: 50,
color: e ? Colors.white : Colors.red,
);
},
),
TextButton(
onPressed: () {
myListWidgetState = myListWidgetState.map((e) => !e).toList();
setState(() {});
print(isColorWhie);
},
child: const Text(
'tab to Change color',
style: TextStyle(color: Colors.white),
),
),
TextButton(
onPressed: () {
setState(() {
isColorWhie = !isColorWhie;
});
print(isColorWhie);
},
child: const Text(
'tab to Change base color',
style: TextStyle(color: Colors.white),
),
),
],
),
),
),
);
}
}
Since you create a container as an object in GestureDetector and save it to your list, it will not change. It is now permanently saved (of course as long as you do not delete the element) as an entry in your list.
Your logic works exactly as you programmed it. For example, if you were to recompile the app and press the TextButton and then anywhere on your screen, a white container would also appear.
If you want to dynamically change the color of all containers at once, then you can do the following:
class _TestState extends State<Test> {
int containerCounter = 0;
late bool isColorWhie = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
containerCounter++;
});
},
child: Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 50,
child: ListView.builder(
shrinkWrap: true,
itemCount: containerCounter,
itemBuilder: ((context, index) {
return Container(
height: 50,
color: isColorWhie ? Colors.white : Colors.red,
);
}),
),
),
TextButton(
onPressed: () {
setState(() {
isColorWhie = !isColorWhie; // here never update
});
},
child: const Text(
'tab to Change color',
style: TextStyle(color: Colors.white),
))
],
),
),
),
);
}
}

Open Drawer of current (topmost) route's Scaffold

I'm writing an audio player. Like most media players (Youtube, Spotify, etc), I want a "remote" overlay on the screen while media is playing. No matter what the user is doing, they should be able to control the media.
I accomplished that with a Stack under MaterialApp
MaterialApp(
title: 'MyApp',
navigatorObservers: [gRouteObserver],
routes: appRoutes,
builder: (context, child) {
return Stack(children: [
child!,
Positioned(
bottom: 55,
width: MediaQuery.of(context).size.width,
child: StatefulBuilder(builder: (context, _setState) {
gPlayer.widgetSBRefresher = _setState;
return gPlayer.started ? gPlayer.widget : const SizedBox(height: 0);
}))
]);
});
gPlayer.widget references this
class MiniPlayer extends StatefulWidget {
const MiniPlayer({Key? key}) : super(key: key);
#override
State<MiniPlayer> createState() => MiniPlayerState();
}
class MiniPlayerState extends State<MiniPlayer> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(context) {
super.build(context);
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.black,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Column(
children: [
Material(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AvatarAlone(id: gPlayer.current!.owner),
Expanded(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Text(gPlayer.playing
? "Now Playing"
: "Paused"),
),
),
// here is the code I'll
// be talking about -->
IconButton(
color: Colors.white,
iconSize: 20,
icon: const Icon(MyIcons.bookmark),
onPressed: gPlayer.bookmarkBuilder,
),
InkWell(child: Icon(gPlayer.playing ? MyIcons.pauseCircle : MyIcons.playCircle, size: 50), onTap: gPlayer.playPause)
],
),
),
],
),
),
),
));
}
refresh() {
setState(() {});
}
}
I used a code comment to point out this icon button.
IconButton(
color: Colors.white,
iconSize: 20,
icon: const Icon(MyIcons.bookmark),
onPressed: gPlayer.bookmarkBuilder,
),
So, when this widget is open and the app is on the home route ("/"), I can do
bookmarkBuilder() {
Scaffold.of(gScaffApp.currentContext!).openDrawer();
}
and it will open the drawer.
I've attached the same drawers to all my routes' scaffolds.
When other routes are up, with their own scaffolds, I want bookmarkBuilder to open the drawer on the topmost route. But I can't quite figure out how.
So I have a working solution to this, but I don't love it.
I created a global variable, gScaffs, with gScaffApp as the first element.
List<GlobalKey<ScaffoldState>> gScaffs = [gScaffApp];
My secondary routes all use the same base scaffold widget
class _CardScaffoldState extends State<CardScaffold> {
#override
initState() {
super.initState();
gScaffs.add(GlobalKey());
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(key: gScaffs.last,
drawer: DrawerBookmarks()
...
And the dispose method looks like this.
#override
dispose() {
super.dispose();
gScaffs.removeLast();
}
And then, in my bookmarkBuilder function, I have this.
It's not clear to me why, but gScaffApp needs the drawer triggered one way, while the CardScaffolds need the drawer triggered the other way.
bookmarkBuilder() {
if (gScaffApp == gScaffs.last) {
Scaffold.of(gScaffApp.currentContext!).openDrawer();
} else {
gScaffs.last.currentState!.openDrawer();
}
}

Is there a way/workaround to show a snackbar on top of the blurred dialog with Flutter?

Below is the screenshot of the current situation/problem. I have a dialog that has a blurred background. I want to show a snackbar when the user clicks the "copy referral link" button. However, since I put a blurred background on the dialog, snackbar also remains behind the background.
What I want is to display the snackbar without blurring it when the user clicks the button. How can I achieve this result? The background should be blurred always but I just need to show the snackbar on top of that blurriness when the user clicks the button.
Here's the image url that shows the current problem
you can bring up the dialog by creating a custom one, like my code below
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isShow = false;
void _incrementCounter() {
setState(() {
isShow = !isShow;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: [
ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: const FlutterLogo(),
),
isShow
? GestureDetector(
onTap: _incrementCounter,
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.black38,
alignment: Alignment.center,
child: GestureDetector(
onTap: () {},
child: AlertDialog(
content: const Text("dsd"),
actions: [
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Awesome Snackbar!'),
action: SnackBarAction(
label: 'Action',
onPressed: () {
// Code to execute.
},
),
),
);
},
child: const Text("Show Snackbar"),
)
],
),
),
),
)
: const SizedBox()
],
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.notifications),
),
);
}
}
screenshot : https://i.stack.imgur.com/inX4I.png
I can't display the image due to lack of reputation points

How do I create curved bottom navigation bar in flutter

In my ongoing project, I need a curved bottom navigation bar. I have tried with curved_navigation_bar package. The result I got that's matched 80% of my requirements. The problem I have facing I can't make the curved position transparent.
This is the picture what I'm getting
This is the picture that I need
Here I've attached two pictures, First picture is what I tried myself I indicate the curved position that I need to make transparent and want to see the bottom list view like the second attached picture. Can anyone help me to reach to requirements.
my code:
import 'package:flutter/material.dart';
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
void main() => runApp(const App());
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: BottomNavBar(),
);
}
}
class BottomNavBar extends StatefulWidget {
const BottomNavBar({Key? key}) : super(key: key);
#override
_BottomNavBarState createState() => _BottomNavBarState();
}
class _BottomNavBarState extends State<BottomNavBar> {
int _page = 0;
final GlobalKey<CurvedNavigationBarState> _bottomNavigationKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: CurvedNavigationBar(
key: _bottomNavigationKey,
index: 0,
height: 60.0,
items: <Widget>[
bottomItem(
title: "Wish List", index: 0, icon: Icons.favorite_border),
bottomItem(title: "Home", index: 1, icon: Icons.home),
bottomItem(title: "My Cart", index: 2, icon: Icons.shopping_cart),
],
color: Colors.black,
buttonBackgroundColor: Colors.white,
backgroundColor: Colors.blue,
animationCurve: Curves.easeInOut,
animationDuration: const Duration(milliseconds: 600),
onTap: (index) {
setState(() {
_page = index;
});
},
letIndexChange: (index) => true,
),
body: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return Container(
height: 150,
color: Colors.primaries[index % Colors.primaries.length],
child: FittedBox(
child: Text(index.toString()),
),
);
}));
}
Widget bottomItem(
{required int index, required String title, required IconData icon}) {
if (index == _page) {
return Icon(
icon,
size: 26,
color: Colors.black,
);
} else {
return Padding(
padding: const EdgeInsets.only(top: 6.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 22,
color: Colors.white,
),
const SizedBox(height: 5),
Text(
title,
style: const TextStyle(color: Colors.white),
)
],
),
);
}
}
}
Make these two changes to your code:
Change the background of your CurvedNavigationBar
backgroundColor: Colors.transparent
Add this to Your Scaffold
extendBody: true,
Does backgroundColor: Colors.transparent fit your usecase?
I have used curved navigation bar. You can make background transparent but there is no option to change curved angles and space between that animation area. You can try fluid_bottom_nav_bar package.

How do I programmatically simulate onTap on a button in Flutter?

For example:
// Update: This GestureDetector is embedded inside a third party package
// that will invoke a series of animation along with the onTap button
GestureDetector(
onTap: () => print('Hey There!'),
child: Widget1(),
)
// Then another place in the same screen
GestureDetector(
onDoubleTap: () {
//Call the onTap of Widget1's GestureDetector
print('I'm Here');
}
child: Widget2(),
)
What I wanted is when a user double tap Widget2, it will also invoke the onTap call back of Widget1.
Update:
So I do not want to just invoke a function passed into the onTap of GestureDetector of Widget1, but rather to programmatically tap the onTap of Widget1's GestureDetector
How do I do that?
You can do something like this -
Create your gesture detector -
GestureDetector gestureDetector = GestureDetector(
onTap: () {
setState(() {
_lights = !_lights;
});
},
child: Container(
color: Colors.yellow.shade600,
padding: const EdgeInsets.all(8),
child: const Text('TURN LIGHTS ON'),
),
);
Create a button (or any widgetthat you would like to use) to call onTap on GestureDetector gestureDetector.onTap() just like you call method on another widget. (I am using a FlatButton here)-
FlatButton(
color: Colors.blue,
textColor: Colors.white,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
padding: EdgeInsets.all(8.0),
onPressed: () {
//Trigger the GestureDetector onTap event.
gestureDetector.onTap();
},
child: Text("Click Here"),
),
Now you can click on the FlatButton to call the onTap event on GestureDetector.
Here is the complete example -
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Gesture Detector On Tap'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _lights = false;
#override
Widget build(BuildContext context) {
GestureDetector gestureDetector = GestureDetector(
onTap: () {
setState(() {
_lights = !_lights;
});
},
child: Container(
color: Colors.yellow.shade600,
padding: const EdgeInsets.all(8),
child: const Text('TURN LIGHTS ON'),
),
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
alignment: FractionalOffset.center,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
Icons.lightbulb_outline,
color: _lights ? Colors.yellow.shade600 : Colors.black,
size: 60,
),
),
gestureDetector,
SizedBox(height: 50.0),
FlatButton(
color: Colors.blue,
textColor: Colors.white,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
padding: EdgeInsets.all(8.0),
onPressed: () {
gestureDetector.onTap();
},
child: Text("Click Here"),
),
],
),
),
),
);
}
}
You will get something like this -
Update: So I do not want to just invoke a function passed into the onTap of GestureDetector of Widget1, but rather to programmatically tap the onTap of Widget1's GestureDetector
The purpose of onTap is to call the callback function inside the onTap. So I'm not sure why you just want to tap the button other than invoking functions that should be called when tapping that button (Can you elaborate on this?).
If you want to simulate the tap for testing, you can do that with Flutter Driver using driver.tap()
After several false starts, this is what worked for me. I use Riverpod, and formFocusIdProvider in this example code is a simple StateProvider.
I'm actually not clear why I needed to add the delay - but without that the behavior was unpredictable with the widget repaint.
This code is in the build method.
ref.listen(formFocusIdProvider, (previous, next) {
if (<some condition>) {
Future.delayed(const Duration(milliseconds: 200), () {
if (mounted) {
onTapFunction();
}
});
}
});
just make the first one's function separately
void firstFunction(){
print('hey There!');
}
like this, then call it in the second widget
so your code will look like this:
GestureDetector(
onTap: () => firstFunction(),
child: Widget1(),
)
// Then another place in the same screen
GestureDetector(
onDoubleTap: () {
firstFunction();
print('I'm Here');
}
child: Widget2(),
)