vrouter
vrouter copied to clipboard
vRedirector.historyBack() does not use reverse transition animation
In the VRouter body, I defined buildTransition, as well as onPop and onPopSystem.
Future<void> _onPop(VRedirector vRedirector) async {
print('_onPop');
if (vRedirector.historyCanBack()) {
vRedirector.historyBack();
}
}
However, in the case of vRedirector.historyBack(), the animation in the reverse direction is not played. Inside widgets, context.vRouter.historyBack() works as expected, with reverse animation.
onPressed: () {
if (context.vRouter.historyCanBack()) {
context.vRouter.historyBack();
}
},
Any idea why the reverse animation isn't happening? Or at least a workaround.
Source code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';
import 'package:mobileway/models/category_model.dart';
import 'package:mobileway/utils/constants.dart';
import 'package:pressable/pressable.dart';
import 'package:vrouter/vrouter.dart';
import '../../bloc/content_bloc/content_bloc.dart';
import '../../models/group_model.dart';
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
final int? tabIndex;
final double height;
const CustomAppBar({Key? key, required this.tabIndex, required this.height})
: super(key: key);
@override
State<CustomAppBar> createState() => _CustomAppBarState();
@override
// TODO: implement preferredSize
Size get preferredSize => Size.fromHeight(height);
}
class _CustomAppBarState extends State<CustomAppBar>
with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
String goBackTitle = 'No name';
bool isShopListVisible = widget.tabIndex == 1 && context.vRouter.path != "/shops";
if (isShopListVisible) {
final regExp = RegExp(r'[0-9]+$');
String path = context.vRouter.path;
RegExpMatch? mathId = regExp.firstMatch(path);
int titleId = int.parse(path.substring(mathId!.start));
if (path.contains('group')) {
final List<GroupModel> groups =
(context.read<ContentBloc>().state as ContentLoadedState).groups;
GroupModel groupModel = groups.firstWhere((element) => element.id == titleId);
goBackTitle = groupModel.name;
} else if (path.contains('category')) {
final List<CategoryModel> categories =
(context.read<ContentBloc>().state as ContentLoadedState)
.categories;
CategoryModel categoryModel = categories.firstWhere((element) => element.id == titleId);
goBackTitle = categoryModel.name;
}
}
double sBarHeight = MediaQuery.of(context).viewPadding.top;
return AppBar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
shadowColor: Theme.of(context).hoverColor.withOpacity(0.1),
elevation: 0,
scrolledUnderElevation: 10,
flexibleSpace: Column(
children: [
Padding(
padding: EdgeInsets.only(
bottom: kSmallPadding,
top: sBarHeight + kSmallPadding,
left: kDefaultPadding,
right: kDefaultPadding),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
SvgPicture.asset(
'assets/icons/logo.svg',
width: 50.r,
height: 50.r,
),
SizedBox(width: kSmallPadding),
if (widget.tabIndex == 3) ...[
Padding(
padding: EdgeInsets.symmetric(horizontal: kSmallPadding),
child: Text('Mobile Way',
style: Theme.of(context)
.textTheme
.headline5!
.copyWith(fontWeight: FontWeight.w600)),
),
] else ...[
Expanded(
child: Container(
height: 50.h,
child: CupertinoTextField(
suffix: Pressable.opacity(
onPressed: () {},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 14.h),
child: SvgPicture.asset(
'assets/icons/search.svg',
width: 20.r,
height: 20.r,
color: Theme.of(context).primaryColor,
),
),
),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color:
Theme.of(context).hoverColor.withOpacity(0.25),
spreadRadius: -6,
blurRadius: 13,
// changes position of shadow
)
],
borderRadius: BorderRadius.circular(14.r),
color: Theme.of(context).scaffoldBackgroundColor,
),
textAlignVertical: TextAlignVertical.center,
padding: const EdgeInsets.symmetric(horizontal: 14),
),
))
],
],
),
),
if (isShopListVisible) ...[
Pressable.opacity(
theme: const PressableOpacityTheme(opacityFactor: 0.6),
onPressed: () {
if (context.vRouter.historyCanBack()) {
context.vRouter.historyBack();
}
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: kDefaultPadding),
SvgPicture.asset(
'assets/icons/back-button.svg',
width: 12.r,
height: 16.r,
color: Theme.of(context).primaryColor,
),
SizedBox(width: kSmallPadding),
Flexible(
child: Text(
goBackTitle,
maxLines: 1,
style: Theme.of(context).textTheme.headline2,
overflow: TextOverflow.ellipsis,
),
),
],
),
)
],
],
),
);
}
}
The animation in the reverse direction only plays when a page is removed from the top of the stack (which is what happens with pop
but which can also be triggered by to
if you go to the page bellow the current one. This is actually something which is handled by the Flutter navigator through the TransitionDelegate
. This is not something I expose in VRouter
but I could if you think this would solve your issue. You could try that by forking VRouter and passing the TransitionDelegate
down. Let my know if you need help/have a PR :)
would this enable arbitrary pop transitions? So the user has the impression of popping the stack while he is actually switching to another nested route?
TransitionDelegate
gives you full control so I guess you could achieve this effect if you wanted to
How can I access TransitionDelegate
through vRouter, please?
The animation in the reverse direction only plays when a page is removed from the top of the stack (which is what happens with
pop
but which can also be triggered byto
if you go to the page bellow the current one. This is actually something which is handled by the Flutter navigator through theTransitionDelegate
. This is not something I expose inVRouter
but I could if you think this would solve your issue. You could try that by forking VRouter and passing theTransitionDelegate
down. Let my know if you need help/have a PR :)
I did not read well .. 😊 I would like to try a PR on this. Can you give me a push in the right direction? @lulupointu
Sure awesome!
For a first version you can have a look at how VRouter.observers
are implemented. Though VRouter.observers
are harder because they need to be unique per navigator so I have to place them into a proxy class.
What you would have to do for transitionDelegate
is basically the same but simpler.
The next step would be to check whether it works for your use case. My main concern is that you might want to be able to access something like previousVRouterData
and newVRouterData
to be able to dynamically change the transition. To achieve this you have 2 possible design:
- Instead of
transitionDelegate
you create atransitionDelegateBuilder
, a function which gives 1 parameter (which is a class with thepreviousVRouterData
andnewVRouterData
, maybe it could be namedVTransitionData
) - Or you create a
VTransitionDelegate
class which would be basically the same asTransitionDelegate
but would includepreviousVRouterData
andnewVRouterData
in theresolve
callback.
Solution 1 might be simpler so I would start with this and see how it feels when using it.
Let me know if you need more help 😊
I was able to pass a working transitionDelegate into vrouter_delegate.dart.
have a look here: https://github.com/lulupointu/vrouter/pull/217/files
But I don't understand how to proceed to either case 1. or 2. you mentioned from here.
Where could previousVRouterData
and newVRouterData
come into play?
Thank you for your support and respect maximum for the library itself :)
Hi, I'm just a random user who discovered this package recently. I've been researching this as well. If you've done the work of exposing the transitionDelegate, maybe just passing this delegate could work: https://github.com/slovnicki/beamer/blob/c3795ab39197cc3429c54c5ec3fa6a1202ddba88/package/lib/src/transition_delegates.dart#L45 ? They capture exiting routes and mark them as pop, which should trigger the reverse animation of VRouter, if I understand correctly. I do not have the skills to foresee what will happen, and I would struggle forking and testing this myself but I thought I'd share this lead here :)