Flutter - onUnknownRoute does not get triggered after using flutter build web - flutter

So, I built a website using flutter web. Everything works fine when I run using flutter run command, the onUnknownRoute gets triggered when I type something in the URL that does not exist. But when I build my project using the flutter build web command, the onUnknownRoute never gets triggered. I have uploaded my project on github pages but onUnknownRoute never gets triggered. I don't even understand what the error could be so, I don't know what exactly should I be posting here as the error-prone code. I shall be delighted if I could get some help. Thank You.
The main.dart
import 'package:flutter/material.dart';
import 'package:app/constants.dart';
import 'package:app/provider/themeProvider.dart';
import 'package:app/provider/themeStyles.dart';
import 'package:app/sections/error404.dart';
import 'package:app/sections/getInTouch/getInTouch.dart';
import 'package:app/sections/mainSection.dart';
import 'package:app/sections/serviceDetails/serviceDetails.dart';
import 'package:app/utils/routeConfiguration.dart';
import 'package:provider/provider.dart';
import 'package:url_strategy/url_strategy.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
setPathUrlStrategy();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ThemeProvider _themeProvider = ThemeProvider();
void getCurrentAppTheme() async {
_themeProvider.lightTheme = await _themeProvider.darkThemePref.getTheme();
}
#override
void initState() {
getCurrentAppTheme();
super.initState();
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ThemeProvider()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'App',
theme: ThemeStyles.themeData(_themeProvider.lightTheme, context),
onGenerateRoute: RouteConfiguration.onGenerateRoute,
initialRoute: MainPage.Route,
onUnknownRoute: (settings) {
return CustomPageRoute(
builder: (_) => ErrorPage(),
// ignore: prefer_const_constructors
settings: RouteSettings(name: ErrorPage.Route));
},
),
);
}
}
The routeConfiguration.dart
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:app/sections/error404.dart';
import 'package:app/sections/mainSection.dart';
import 'package:app/utils/Path.dart';
class RouteConfiguration {
/// List of [Path] to for route matching. When a named route is pushed with
/// [Navigator.pushNamed], the route name is matched with the [Path.pattern]
/// in the list below. As soon as there is a match, the associated builder
/// will be returned. This means that the paths higher up in the list will
/// take priority.
static List<Path> paths = [
Path(
r'^' + ErrorPage.Route + '\$',
(context, match) => ErrorPage(),
),
Path(
r'^' + ErrorPage.Route + '/\$',
(context, match) => ErrorPage(),
),
];
/// The route generator callback used when the app is navigated to a named
/// route. Set it on the [MaterialApp.onGenerateRoute] or
/// [WidgetsApp.onGenerateRoute] to make use of the [paths] for route
/// matching.
static Route<dynamic> onGenerateRoute(RouteSettings settings) {
if (settings.name == "/") {
return CustomPageRoute(
builder: (context) => MainPage(),
settings: settings,
);
}
for (Path path in paths) {
final regExpPattern = RegExp(path.pattern!);
if (regExpPattern.hasMatch(settings.name!)) {
final firstMatch = regExpPattern.firstMatch(settings.name!);
final match = (firstMatch!.groupCount == 1) ? firstMatch.group(1) : null;
return CustomPageRoute(
builder: (context) {
return path.builder(context, match);
},
settings: RouteSettings(name: ErrorPage.Route),
);
}
}
// If no match was found, we let [WidgetsApp.onUnknownRoute] handle it.
return CustomPageRoute(
builder: (_) => ErrorPage(),
settings: RouteSettings(name: ErrorPage.Route));
}
}
class CustomPageRoute extends PageRouteBuilder {
final Widget Function(dynamic)? builder;
final RouteSettings settings;
CustomPageRoute({this.builder, required this.settings})
: super(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
builder!.call(context),
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: Tween<double>(
begin: 0.5,
end: 1.0,
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
),
),
child: child,
),
),
settings: settings);
}
the error404.dart
// ignore_for_file: prefer_const_constructors
import 'dart:ui';
import 'package:flutter/rendering.dart';
import 'package:app/animations/glitchAnimation.dart';
import 'package:app/provider/themeProvider.dart';
import 'package:app/sections/mainSection.dart';
import 'package:app/utils/animatedUFO.dart';
import 'package:app/utils/star_field.dart';
import 'package:app/widget/adaptiveText.dart';
import 'package:mouse_parallax/mouse_parallax.dart';
import 'package:provider/provider.dart';
import 'package:seo_renderer/seo_renderer.dart';
import 'package:flutter/material.dart';
import 'package:app/animations/entranceFader.dart';
import 'package:app/sections/navBar/navBarLogo.dart';
class ErrorPage extends StatefulWidget {
static const String Route = "/404";
#override
_ErrorPageState createState() => _ErrorPageState();
}
class _ErrorPageState extends State<ErrorPage>
with SingleTickerProviderStateMixin {
final globalKey = GlobalKey<ScaffoldState>();
ThemeProvider _themeProviders = ThemeProvider();
bool isPressed = false;
bool _isScrollingDown = false;
ScrollController _scrollController = ScrollController();
static const double idleSpeed = .2;
static const int starAnimDurationIn = 4500;
ValueNotifier<double> _speedValue = ValueNotifier(idleSpeed);
late AnimationController _starAnimController;
late Animation<double> _starAnimSequence;
#override
void initState() {
_scrollController = _themeProviders.scroll;
_scrollController.addListener(() {
if (_scrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
if (!_isScrollingDown) {
_isScrollingDown = true;
setState(() {});
}
}
if (_scrollController.position.userScrollDirection ==
ScrollDirection.forward) {
if (_isScrollingDown) {
_isScrollingDown = false;
setState(() {});
}
}
});
_starAnimController = AnimationController(
vsync: this,
duration: Duration(milliseconds: starAnimDurationIn),
reverseDuration: Duration(milliseconds: starAnimDurationIn ~/ 3),
);
_starAnimController.addListener(() {
_speedValue.value = _starAnimSequence.value;
});
//Create an animation sequence that moves our stars back, then forwards, then to rest at 0.
//This will be played each time we load a detail page, to create a flying through space transition effect
_starAnimSequence = TweenSequence([
TweenSequenceItem<double>(
tween: Tween<double>(begin: idleSpeed, end: -2)
.chain(CurveTween(curve: Curves.easeOut)),
weight: 20.0,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: -2, end: 20)
.chain(CurveTween(curve: Curves.easeOut)),
weight: 30.0,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 20, end: 0)
.chain(CurveTween(curve: Curves.easeOut)),
weight: 50.0,
)
]).animate(_starAnimController);
super.initState();
// WidgetsBinding.instance?.addPostFrameCallback((_) {
// if (regExpBots.hasMatch(window.navigator.userAgent.toString()) && (globalKey.currentState != null)) {
// globalKey.currentState.openDrawer();
// }
// });
}
#override
void dispose() {
_scrollController.dispose();
_scrollController.removeListener(() {});
_starAnimController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final _themeProv = Provider.of<ThemeProvider>(context);
double _height = MediaQuery.of(context).size.height;
int starCount = 400;
return Container(
child: Stack(
children: [
Container(
width: double.maxFinite,
height: double.maxFinite,
child: ImageRenderer(
alt: 'Background Image For Decoration',
link: 'assets/logos/i8.jpg',
child: Image.asset(
'assets/logos/i8.jpg',
fit: BoxFit.cover,
),
),
),
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 30.0,
sigmaY: 30.0,
),
child: Scaffold(
key: globalKey,
extendBodyBehindAppBar: true,
backgroundColor: _themeProv.lightTheme
? Colors.white
: Colors.black.withOpacity(0.0),
appBar: (MediaQuery.of(context).size.width < 860)
? AppBar(
automaticallyImplyLeading: false,
iconTheme: IconThemeData(
color: _themeProv.lightTheme
? Color(0xff1C1C28)
: Colors.white),
elevation: 0,
backgroundColor: Colors.transparent,
actions: [
GestureDetector(
onTap: () {
Navigator.pushNamedAndRemoveUntil(
context, MainPage.Route, (c) {
return false;
});
},
child: NavBarLogo(),
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.05,
)
],
)
: _appBarTabDesktop(_themeProv),
body: Stack(
children: [
ValueListenableBuilder<double>(
valueListenable: _speedValue,
builder: (context, value, child) {
//Scrolling star background
return StarField(starSpeed: value, starCount: starCount);
},
),
Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 27.0),
child: ParallaxStack(
resetOnExit: true,
useLocalPosition: true,
referencePosition: 0.6,
dragCurve: Curves.easeIn,
resetCurve: Curves.bounceOut,
layers: [
ParallaxLayer(
yRotation: 0.80,
xRotation: 0.80,
xOffset: 90,
yOffset: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: TextRenderer(
text: GlithEffect(
child: AdaptiveText(
"404",
style: TextStyle(
fontSize: _height * 0.1,
fontFamily: 'Montserrat',
fontWeight: FontWeight.w900,
color: Colors.white,
),
),
),
),
),
SizedBox(
height: _height * 0.02,
),
TextRenderer(
text: AdaptiveText(
"We searched as far as we could, but were unable to find the page you were looking for.\nSorry.",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'Ubuntu',
fontSize: _height * 0.02,
color: Colors.white,
height: 2.0,
),
),
),
],
),
),
],
),
),
),
],
),
),
),
],
),
);
}
AppBar _appBarTabDesktop(ThemeProvider _themeProv) {
return AppBar(
elevation: 0.0,
backgroundColor:
_themeProv.lightTheme ? Colors.white : Colors.transparent,
automaticallyImplyLeading: false,
centerTitle: false,
title: MediaQuery.of(context).size.width < 780
? EntranceFader(
duration: Duration(milliseconds: 250),
offset: Offset(0, -10),
delay: Duration(seconds: 3),
child: GestureDetector(
onTap: () {
Navigator.pushNamedAndRemoveUntil(context, MainPage.Route,
(c) {
return false;
});
},
child: NavBarLogo(
height: 37.0,
),
))
: EntranceFader(
offset: Offset(0, -10),
duration: Duration(milliseconds: 250),
delay: Duration(milliseconds: 100),
child: GestureDetector(
onTap: () {
Navigator.pushNamedAndRemoveUntil(context, MainPage.Route,
(c) {
return false;
});
},
child: NavBarLogo(
height: MediaQuery.of(context).size.height * 0.065,
),
),
),
);
}
}
Also, I read in some other solution for a custom 404 page for github pages that I need a custom domain for it to work. So, is that what is causing my problem.

You should use Navigator v2 for web. it has more features and makes working with web routes very easy.

Related

Flutter rebuild a TweenAnimationBuilder

I want to make a flipping animation with TweenAnimationBuilder, a container will flip over and change the color. I want to add a button when user click on it, the container will flip over again and change into another colour.
Here is my code:
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isBack = true;
Color backColor = Colors.green;
Color topColor = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
box(),
SizedBox(height: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(25, 10),
elevation: 10,
),
onPressed: () {
setState(() {
backColor = Colors.red;
topColor = Colors.blue;
});
},
child:
Text('change to blue', style: TextStyle(fontSize: 16))),
],
),
),
),
);
}
Widget box() {
print('building');
return TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: pi),
duration: Duration(seconds: 1),
builder: (BuildContext context, double value, _) {
print(value);
if (value >= (pi / 2)) {
isBack = false;
} else {
isBack = true;
}
return (Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(value),
child: Container(
width: 100,
height: 100,
child: isBack
? Container(
color: backColor,
)
: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateY(
pi),
child: Container(
color: topColor,
),
)
),
));
});
}
}
At the first build, the tween value will start from the beginning:
building
0
0
0
0.13080335172486462
0.19619246121668257
0.2180893620122034
...
3.141592653589793
but when I click on the button to change the color, it will not start again from the begin value, it just stays at 3.14:
building
3.141592653589793
Right now the button will only change the color of the container, but it will not flip again.
I suppose after the setstate function, the tween value will restart again at 0, why won't it do so?
Can anybody explain it please?
All you need is an AnimationController and AnimatedBuilder to control the animation.
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
bool isBack = true;
Color backColor = Colors.blue;
Color topColor = Colors.red;
late AnimationController _animationController;
late Animation _rotationAnimation;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 1));
_rotationAnimation =
Tween<double>(begin: 0, end: pi).animate(_animationController);
_animationController.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
box(),
SizedBox(height: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(25, 10),
elevation: 10,
),
onPressed: () {
setState(() {
backColor = Colors.red;
topColor = Colors.blue;
});
// if (_animationController.isDismissed) {
// _animationController.forward();
// } else if (_animationController.isCompleted) {
// _animationController.reverse();
// }
_animationController.forward(from: 0.0);
},
child:
Text('change to blue', style: TextStyle(fontSize: 16))),
],
),
),
),
);
}
Widget box() {
return AnimatedBuilder(
animation: _animationController,
// tween: Tween<double>(begin: 0, end: pi),
// duration: Duration(seconds: 1),
builder: (_, __) {
// print(value);
if (_rotationAnimation.value >= (pi / 2)) {
isBack = false;
} else {
isBack = true;
}
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(_rotationAnimation.value),
child: Container(
width: 100,
height: 100,
child: isBack
? Container(
color: backColor,
)
: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..rotateY(pi),
child: Container(
color: topColor,
),
),
),
);
});
}
}

Flutter adding Sliver to DraggableScrollableSheet

after adding SliverAppBar to DraggableScrollableSheet i cant scroll sheet to up or down of screen, but list inside SliverAppbar work fine,
full source code:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: HomePage(),
));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
BorderRadiusTween borderRadius;
Duration _duration = Duration(milliseconds: 500);
Tween<Offset> _tween = Tween(begin: Offset(0, 1), end: Offset(0, 0));
double _height, min = 0.1, initial = 0.3, max = 1;
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: _duration);
borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(10.0),
end: BorderRadius.circular(0.0),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('DraggableScrollableSheet'),
),
floatingActionButton: GestureDetector(
child: FloatingActionButton(
child: AnimatedIcon(icon: AnimatedIcons.menu_close, progress: _controller),
elevation: 5,
backgroundColor: Colors.deepOrange,
foregroundColor: Colors.white,
onPressed: () async {
if (_controller.isDismissed)
_controller.forward();
else if (_controller.isCompleted) _controller.reverse();
},
),
),
body: SizedBox.expand(
child: Stack(
children: <Widget>[
FlutterLogo(size: 500),
SizedBox.expand(
child: SlideTransition(
position: _tween.animate(_controller),
child: DraggableScrollableSheet(
minChildSize: min, // 0.1 times of available height, sheet can't go below this on dragging
maxChildSize: max, // 0.7 times of available height, sheet can't go above this on dragging
initialChildSize: initial, // 0.1 times of available height, sheet start at this size when opened for first time
builder: (BuildContext context, ScrollController controller) {
if (controller.hasClients) {
var dimension = controller.position.viewportDimension;
_height ??= dimension / initial;
if (dimension >= _height * max * 0.9)
_onWidgetDidBuild(() {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('ON TOP'),
duration: Duration(seconds: 3),
));
});
else if (dimension <= _height * min * 1.1)
_onWidgetDidBuild(() {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('ON BOTTOM'),
duration: Duration(seconds: 3),
));
});
}
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
return ClipRRect(
borderRadius: borderRadius.evaluate(CurvedAnimation(parent: _controller, curve: Curves.ease)),
child: Container(
color: Colors.blue[800],
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text("What's Up?"),
backgroundColor: Colors.orange,
automaticallyImplyLeading: false,
primary: false,
floating: true,
pinned: true,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, idx) => ListTile(
title: Text("Nothing much"),
subtitle: Text("$idx"),
),
childCount: 100,
),
)
],
),
),
);
},
);
},
),
),
),
],
),
),
);
}
_onWidgetDidBuild(Function callback) {
WidgetsBinding.instance.addPostFrameCallback((_) {
callback();
});
}
}
CustomScrollView(
controller: controller, // you missed this
...
)

Flutter using BorderRadiusTween for seperated ClipRRect border radiuses

in this code we can easily set border radius for all corners, such as
topLeft, topRight, bottomLeft, bottomRight with one line in code for example:
borderRadius: borderRadius.evaluate(CurvedAnimation(parent: _controller, curve: Curves.ease)),
now how can i use that for separated corners? for example:
borderRadius: BorderRadius.only(
topLeft: borderRadius.evaluate(CurvedAnimation(parent: _controller, curve: Curves.ease)),
topRight: borderRadius.evaluate(CurvedAnimation(parent: _controller, curve: Curves.ease)),
),
full source code:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: HomePage(),
));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
BorderRadiusTween borderRadius;
Duration _duration = Duration(milliseconds: 500);
Tween<Offset> _tween = Tween(begin: Offset(0, 1), end: Offset(0, 0));
double _height, min = 0.1, initial = 0.3, max = 1;
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: _duration);
borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(75.0),
end: BorderRadius.circular(0.0),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('DraggableScrollableSheet'),
),
floatingActionButton: GestureDetector(
child: FloatingActionButton(
child: AnimatedIcon(icon: AnimatedIcons.menu_close, progress: _controller),
elevation: 5,
backgroundColor: Colors.deepOrange,
foregroundColor: Colors.white,
onPressed: () async {
if (_controller.isDismissed)
_controller.forward();
else if (_controller.isCompleted) _controller.reverse();
},
),
),
body: SizedBox.expand(
child: Stack(
children: <Widget>[
FlutterLogo(size: 500),
SizedBox.expand(
child: SlideTransition(
position: _tween.animate(_controller),
child: DraggableScrollableSheet(
minChildSize: min, // 0.1 times of available height, sheet can't go below this on dragging
maxChildSize: max, // 0.7 times of available height, sheet can't go above this on dragging
initialChildSize: initial, // 0.1 times of available height, sheet start at this size when opened for first time
builder: (BuildContext context, ScrollController controller) {
if (controller.hasClients) {
var dimension = controller.position.viewportDimension;
_height ??= dimension / initial;
if (dimension >= _height * max * 0.9)
_onWidgetDidBuild(() {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('ON TOP'),
duration: Duration(seconds: 3),
));
});
else if (dimension <= _height * min * 1.1)
_onWidgetDidBuild(() {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('ON BOTTOM'),
duration: Duration(seconds: 3),
));
});
}
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
return ClipRRect(
borderRadius: borderRadius.evaluate(CurvedAnimation(parent: _controller, curve: Curves.ease)),
child: Container(
color: Colors.blue[800],
child: ListView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
),
);
},
);
},
),
),
),
],
),
),
);
}
_onWidgetDidBuild(Function callback) {
WidgetsBinding.instance.addPostFrameCallback((_) {
callback();
});
}
}
You don't need to use Tween or any sort of Animation for it.
Check out this example.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
Duration _duration = Duration(milliseconds: 500);
Tween<Offset> _tween = Tween(begin: Offset(0, 1), end: Offset(0, 0));
static double _origRadius = 80;
double _height, min = 0.1, initial = 0.5, max = 1, _radius = _origRadius;
GlobalKey<ScaffoldState> _key = GlobalKey();
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: _duration, value: 1);
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key,
floatingActionButton: GestureDetector(
child: FloatingActionButton(
child: AnimatedIcon(icon: AnimatedIcons.menu_close, progress: _controller),
elevation: 5,
backgroundColor: Colors.deepOrange,
foregroundColor: Colors.white,
onPressed: () async {
if (_controller.isDismissed)
_controller.forward();
else if (_controller.isCompleted) _controller.reverse();
},
),
),
body: SizedBox.expand(
child: Stack(
children: <Widget>[
FlutterLogo(size: 500),
SizedBox.expand(
child: SlideTransition(
position: _tween.animate(_controller),
child: DraggableScrollableSheet(
minChildSize: min, // 0.1 times of available height, sheet can't go below this on dragging
maxChildSize: max, // 0.7 times of available height, sheet can't go above this on dragging
initialChildSize: initial, // 0.1 times of available height, sheet start at this size when opened for first time
builder: (BuildContext context, ScrollController controller) {
if (controller.hasClients) {
var dimension = controller.position.viewportDimension;
_height ??= dimension / initial;
// this is used for border radius
double initialTop = (_height * max) - _origRadius;
if (dimension > initialTop) {
if (_radius >= 0) {
_radius = _origRadius - (dimension - initialTop);
}
}
}
return ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(_radius), topRight: Radius.circular(_radius)),
child: Container(
height: 500.0,
color: Colors.blue[800],
child: ListView.builder(
controller: controller,
itemCount: 15,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
),
);
},
),
),
),
],
),
),
);
}
}

Flutter - Flip animation - Flip a card over its right or left side based on the tap's location

I've started playing with Flutter and now thinking about the best way how I can implement a card's flipping animation.
I found this flip_card package and I'm trying to adjust its source code to my needs.
Here is the app which I have now:
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(FlipAnimationApp());
class FlipAnimationApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Flip animation"),
),
body: Center(
child: Container(
width: 200,
height: 200,
child: WidgetFlipper(
frontWidget: Container(
color: Colors.green[200],
child: Center(
child: Text(
"FRONT side.",
),
),
),
backWidget: Container(
color: Colors.yellow[200],
child: Center(
child: Text(
"BACK side.",
),
),
),
),
),
),
),
);
}
}
class WidgetFlipper extends StatefulWidget {
WidgetFlipper({
Key key,
this.frontWidget,
this.backWidget,
}) : super(key: key);
final Widget frontWidget;
final Widget backWidget;
#override
_WidgetFlipperState createState() => _WidgetFlipperState();
}
class _WidgetFlipperState extends State<WidgetFlipper> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> _frontRotation;
Animation<double> _backRotation;
bool isFrontVisible = true;
#override
void initState() {
super.initState();
controller = AnimationController(duration: Duration(milliseconds: 500), vsync: this);
_frontRotation = TweenSequence(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween(begin: 0.0, end: pi / 2).chain(CurveTween(curve: Curves.linear)),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(pi / 2),
weight: 50.0,
),
],
).animate(controller);
_backRotation = TweenSequence(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(pi / 2),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: Tween(begin: -pi / 2, end: 0.0).chain(CurveTween(curve: Curves.linear)),
weight: 50.0,
),
],
).animate(controller);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
AnimatedCard(
animation: _backRotation,
child: widget.backWidget,
),
AnimatedCard(
animation: _frontRotation,
child: widget.frontWidget,
),
_tapDetectionControls(),
],
);
}
Widget _tapDetectionControls() {
return Stack(
fit: StackFit.expand,
children: <Widget>[
GestureDetector(
onTap: _leftRotation,
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 1.0,
alignment: Alignment.topLeft,
child: Container(
color: Colors.transparent,
),
),
),
GestureDetector(
onTap: _rightRotation,
child: FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 1.0,
alignment: Alignment.topRight,
child: Container(
color: Colors.transparent,
),
),
),
],
);
}
void _leftRotation() {
_toggleSide();
}
void _rightRotation() {
_toggleSide();
}
void _toggleSide() {
if (isFrontVisible) {
controller.forward();
isFrontVisible = false;
} else {
controller.reverse();
isFrontVisible = true;
}
}
}
class AnimatedCard extends StatelessWidget {
AnimatedCard({
this.child,
this.animation,
});
final Widget child;
final Animation<double> animation;
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
var transform = Matrix4.identity();
transform.setEntry(3, 2, 0.001);
transform.rotateY(animation.value);
return Transform(
transform: transform,
alignment: Alignment.center,
child: child,
);
},
child: child,
);
}
}
Here is how it looks like:
What I'd like to achieve is to make the card flip over its right side if it was tapped on its right half and over its left side if it was tapped on its left half. If it is tapped several times in a row on the same half it should flip over the same side (not back and forth as it is doing now).
So the desired animation should behave as the following one from Quizlet app.
You should know when you tap on the right or left to change the animations dynamically, for that you could use a flag isRightTap. Then, you should invert the values of the Tweens if it has to rotate to one side or to the other.
And the side you should rotate would be:
Rotate to left if the front is visible and you tapped on the left, or, because the back animation is reversed, if the back is is visible and you tapped on the right
Otherwise, rotate to right
Here are the things I changed in _WidgetFlipperState from the code in the question:
_updateRotations(bool isRightTap) {
setState(() {
bool rotateToLeft = (isFrontVisible && !isRightTap) || !isFrontVisible && isRightTap;
_frontRotation = TweenSequence(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween(begin: 0.0, end: rotateToLeft ? (pi / 2) : (-pi / 2))
.chain(CurveTween(curve: Curves.linear)),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(rotateToLeft ? (-pi / 2) : (pi / 2)),
weight: 50.0,
),
],
).animate(controller);
_backRotation = TweenSequence(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(rotateToLeft ? (pi / 2) : (-pi / 2)),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: Tween(begin: rotateToLeft ? (-pi / 2) : (pi / 2), end: 0.0)
.chain(CurveTween(curve: Curves.linear)),
weight: 50.0,
),
],
).animate(controller);
});
}
#override
void initState() {
super.initState();
controller =
AnimationController(duration: Duration(milliseconds: 500), vsync: this);
_updateRotations(true);
}
void _leftRotation() {
_toggleSide(false);
}
void _rightRotation() {
_toggleSide(true);
}
void _toggleSide(bool isRightTap) {
_updateRotations(isRightTap);
if (isFrontVisible) {
controller.forward();
isFrontVisible = false;
} else {
controller.reverse();
isFrontVisible = true;
}
}
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp();
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage();
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _toggler = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(actions: [
TextButton(
onPressed: _onFlipCardPressed,
child: const Text('change', style: TextStyle(color: Colors.white)),
)
]),
body: Center(
child: SizedBox.square(
dimension: 140,
child: FlipCard(
toggler: _toggler,
frontCard: AppCard(title: 'Front'),
backCard: AppCard(title: 'Back'),
),
),
),
);
}
void _onFlipCardPressed() {
setState(() {
_toggler = !_toggler;
});
}
}
class AppCard extends StatelessWidget {
final String title;
const AppCard({
required this.title,
});
#override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Colors.deepPurple[400],
),
child: Center(
child: Text(
title,
style: const TextStyle(
fontSize: 40.0,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
);
}
}
class FlipCard extends StatelessWidget {
final bool toggler;
final Widget frontCard;
final Widget backCard;
const FlipCard({
required this.toggler,
required this.backCard,
required this.frontCard,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 800),
transitionBuilder: _transitionBuilder,
layoutBuilder: (widget, list) => Stack(children: [widget!, ...list]),
switchInCurve: Curves.ease,
switchOutCurve: Curves.ease.flipped,
child: toggler
? SizedBox(key: const ValueKey('front'), child: frontCard)
: SizedBox(key: const ValueKey('back'), child: backCard),
),
);
}
Widget _transitionBuilder(Widget widget, Animation<double> animation) {
final rotateAnimation = Tween(begin: pi, end: 0.0).animate(animation);
return AnimatedBuilder(
animation: rotateAnimation,
child: widget,
builder: (context, widget) {
final isFront = ValueKey(toggler) == widget!.key;
final rotationY = isFront ? rotateAnimation.value : min(rotateAnimation.value, pi * 0.5);
return Transform(
transform: Matrix4.rotationY(rotationY)..setEntry(3, 0, 0),
alignment: Alignment.center,
child: widget,
);
},
);
}
}
Try this code I've made some changes to your code, now the GestureDetector is divided equally in width on widget so when you tap on the left side of the box it will reverse the animation and if you tap on right side part it will forward the animation.
Widget _tapDetectionControls() {
return Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
flex: 1,
child: GestureDetector(
onTap: _leftRotation,
),
),
Expanded(
flex: 1,
child: GestureDetector(
onTap: _rightRotation,
),
),
],
);
}
void _leftRotation() {
controller.reverse();
}
void _rightRotation() {
controller.forward();
}

flutter notify from top of the screen

I'm trying to figure out how to notify user with alert that comes from top of the screen like normal push notification does.
How can I alert user from top of the screen.
AlertDialog is not customizable so I'm stuck with this. Is there any way to show something like alert or snack bar from top of the screen?
Flutter gives you the possiblity to create notifications with the help of the class Overlay. To animate these entering the screen from the top you can use the SlideTransition in combination with an AnimationController. Here is an example application I created:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton.icon(
icon: Icon(Icons.notifications_active),
label: Text('Notify!'),
onPressed: () {
Navigator.of(context)
.overlay
.insert(OverlayEntry(builder: (BuildContext context) {
return FunkyNotification();
}));
},
),
),
);
}
}
class FunkyNotification extends StatefulWidget {
#override
State<StatefulWidget> createState() => FunkyNotificationState();
}
class FunkyNotificationState extends State<FunkyNotification>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<Offset> position;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 750));
position = Tween<Offset>(begin: Offset(0.0, -4.0), end: Offset.zero)
.animate(
CurvedAnimation(parent: controller, curve: Curves.bounceInOut));
controller.forward();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 32.0),
child: SlideTransition(
position: position,
child: Container(
decoration: ShapeDecoration(
color: Colors.deepPurple,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0))),
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text(
'Notification!',
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
),
),
),
),
),
),
);
}
}
Here you can dismiss notifications using the swipe up or down. This is the perfect notification for promotion in-app.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with TickerProviderStateMixin {
bool _fromTop = true;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.fireplace_outlined),
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.transparent,
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return GestureDetector(
onVerticalDragUpdate: (dragUpdateDetails) {
Navigator.of(context).pop();
},
child: Column(
children: [
SizedBox(height: 40),
Card(
margin:
EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: Container(
height: 100,
child: Image.asset('lib/model/promo.png',
fit: BoxFit.fill),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
),
],
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: anim1.drive(Tween(
begin: Offset(0, _fromTop ? -1 : 1), end: Offset(0, 0))
.chain(CurveTween(curve: Sprung()))),
child: child,
);
},
);
},
),
);
}
}
class Sprung extends Curve {
factory Sprung([double damping = 20]) => Sprung.custom(damping: damping);
Sprung.custom({
double damping = 20,
double stiffness = 180,
double mass = 1.0,
double velocity = 0.0,
}) : this._sim = SpringSimulation(
SpringDescription(
damping: damping,
mass: mass,
stiffness: stiffness,
),
0.0,
1.0,
velocity,
);
final SpringSimulation _sim;
#override
double transform(double t) => _sim.x(t) + t * (1 - _sim.x(1.0));
}