vrouter icon indicating copy to clipboard operation
vrouter copied to clipboard

vRedirector.historyBack() does not use reverse transition animation

Open Oubi256 opened this issue 2 years ago • 8 comments

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,
                  ),
                ),
              ],
            ),
          )
        ],
      ],
    ),
  );
}
}

Oubi256 avatar Jul 05 '22 07:07 Oubi256

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 :)

lulupointu avatar Jul 14 '22 16:07 lulupointu

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?

wizza-smile avatar May 15 '23 10:05 wizza-smile

TransitionDelegate gives you full control so I guess you could achieve this effect if you wanted to

lulupointu avatar May 23 '23 12:05 lulupointu

How can I access TransitionDelegate through vRouter, please?

wizza-smile avatar May 23 '23 17:05 wizza-smile

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 :)

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

wizza-smile avatar May 24 '23 18:05 wizza-smile

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 transitionDelegateis 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 previousVRouterDataand newVRouterData to be able to dynamically change the transition. To achieve this you have 2 possible design:

  1. Instead of transitionDelegateyou create a transitionDelegateBuilder, a function which gives 1 parameter (which is a class with the previousVRouterDataand newVRouterData, maybe it could be named VTransitionData)
  2. Or you create a VTransitionDelegate class which would be basically the same as TransitionDelegatebut would include previousVRouterDataand newVRouterData in the resolve 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 😊

lulupointu avatar May 25 '23 08:05 lulupointu

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 :)

wizza-smile avatar Jun 26 '23 10:06 wizza-smile

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 :)

titaniteChuck avatar Nov 02 '23 12:11 titaniteChuck